blob: afa571a507fbb8bc9e831b2ad28632a1b70855af [file] [log] [blame]
// 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(std::optional<HWND> hwnd,
std::optional<WPARAM> wparam,
std::optional<LPARAM> lparam,
UINT exit_code) {
if (!hwnd.has_value()) {
::PostQuitMessage(exit_code);
} else {
BASE_CHECK(wparam.has_value() && lparam.has_value());
sent_close_messages_[std::make_tuple(*hwnd, *wparam, *lparam)]++;
DispatchMessage(*hwnd, WM_CLOSE, *wparam, *lparam);
}
}
void WindowsLifecycleManager::DispatchMessage(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
PostMessage(hwnd, message, wparam, lparam);
}
bool WindowsLifecycleManager::HandleCloseMessage(HWND hwnd,
WPARAM wparam,
LPARAM lparam) {
if (!process_exit_) {
return false;
}
auto key = std::make_tuple(hwnd, wparam, lparam);
auto itr = sent_close_messages_.find(key);
if (itr != sent_close_messages_.end()) {
if (itr->second == 1) {
sent_close_messages_.erase(itr);
} else {
sent_close_messages_[key]--;
}
return false;
}
if (IsLastWindowOfProcess()) {
engine_->RequestApplicationQuit(hwnd, wparam, lparam,
AppExitType::cancelable);
return true;
}
return false;
}
bool WindowsLifecycleManager::WindowProc(HWND hwnd,
UINT msg,
WPARAM wpar,
LPARAM lpar,
LRESULT* result) {
switch (msg) {
// When WM_CLOSE is received from the final window of an application, we
// send a request to the framework to see if the app should exit. If it
// is, we re-dispatch a new WM_CLOSE message. In order to allow the new
// message to reach other delegates, we ignore it here.
case WM_CLOSE:
return HandleCloseMessage(hwnd, wpar, lpar);
// DWM composition can be disabled on Windows 7.
// Notify the engine as this can result in screen tearing.
case WM_DWMCOMPOSITIONCHANGED:
engine_->OnDwmCompositionChanged();
break;
case WM_SIZE:
if (wpar == SIZE_MAXIMIZED || wpar == SIZE_RESTORED) {
OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
} else if (wpar == SIZE_MINIMIZED) {
OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
}
break;
case WM_SHOWWINDOW:
if (!wpar) {
OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
} else {
OnWindowStateEvent(hwnd, WindowStateEvent::kShow);
}
break;
case WM_DESTROY:
OnWindowStateEvent(hwnd, WindowStateEvent::kHide);
break;
}
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;
}
void WindowsLifecycleManager::BeginProcessingLifecycle() {
process_lifecycle_ = true;
}
void WindowsLifecycleManager::BeginProcessingExit() {
process_exit_ = true;
}
void WindowsLifecycleManager::SetLifecycleState(AppLifecycleState state) {
if (state_ == state) {
return;
}
state_ = state;
if (engine_ && process_lifecycle_) {
const char* state_name = AppLifecycleStateToString(state);
engine_->SendPlatformMessage("flutter/lifecycle",
reinterpret_cast<const uint8_t*>(state_name),
strlen(state_name), nullptr, nullptr);
}
}
void WindowsLifecycleManager::OnWindowStateEvent(HWND hwnd,
WindowStateEvent event) {
// Synthesize an unfocus event when a focused window is hidden.
if (event == WindowStateEvent::kHide &&
focused_windows_.find(hwnd) != focused_windows_.end()) {
OnWindowStateEvent(hwnd, WindowStateEvent::kUnfocus);
}
std::lock_guard guard(state_update_lock_);
switch (event) {
case WindowStateEvent::kShow: {
bool first_shown_window = visible_windows_.empty();
auto pair = visible_windows_.insert(hwnd);
if (first_shown_window && pair.second &&
state_ == AppLifecycleState::kHidden) {
SetLifecycleState(AppLifecycleState::kInactive);
}
break;
}
case WindowStateEvent::kHide: {
bool present = visible_windows_.erase(hwnd);
bool empty = visible_windows_.empty();
if (present && empty &&
(state_ == AppLifecycleState::kResumed ||
state_ == AppLifecycleState::kInactive)) {
SetLifecycleState(AppLifecycleState::kHidden);
}
break;
}
case WindowStateEvent::kFocus: {
bool first_focused_window = focused_windows_.empty();
auto pair = focused_windows_.insert(hwnd);
if (first_focused_window && pair.second &&
state_ == AppLifecycleState::kInactive) {
SetLifecycleState(AppLifecycleState::kResumed);
}
break;
}
case WindowStateEvent::kUnfocus: {
if (focused_windows_.erase(hwnd) && focused_windows_.empty() &&
state_ == AppLifecycleState::kResumed) {
SetLifecycleState(AppLifecycleState::kInactive);
}
break;
}
}
}
std::optional<LRESULT> WindowsLifecycleManager::ExternalWindowMessage(
HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
std::optional<flutter::WindowStateEvent> event = std::nullopt;
// TODO (schectman): Handle WM_CLOSE messages.
// https://github.com/flutter/flutter/issues/131497
switch (message) {
case WM_SHOWWINDOW:
event = wparam ? flutter::WindowStateEvent::kShow
: flutter::WindowStateEvent::kHide;
break;
case WM_SIZE:
switch (wparam) {
case SIZE_MINIMIZED:
event = flutter::WindowStateEvent::kHide;
break;
case SIZE_RESTORED:
case SIZE_MAXIMIZED:
event = flutter::WindowStateEvent::kShow;
break;
}
break;
case WM_SETFOCUS:
event = flutter::WindowStateEvent::kFocus;
break;
case WM_KILLFOCUS:
event = flutter::WindowStateEvent::kUnfocus;
break;
case WM_DESTROY:
event = flutter::WindowStateEvent::kHide;
break;
case WM_CLOSE:
if (HandleCloseMessage(hwnd, wparam, lparam)) {
return NULL;
}
break;
}
if (event.has_value()) {
OnWindowStateEvent(hwnd, *event);
}
return std::nullopt;
}
} // namespace flutter