| // 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 |