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