diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 679275c98a8c0..f37b0279e3c8d 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -3288,8 +3288,6 @@ ORIGIN: ../../../flutter/shell/platform/windows/text_input_manager.h + ../../../ ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/text_input_plugin_delegate.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/windows/window.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/windows/window.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc + ../../../flutter/LICENSE @@ -6060,8 +6058,6 @@ FILE: ../../../flutter/shell/platform/windows/text_input_manager.h FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h FILE: ../../../flutter/shell/platform/windows/text_input_plugin_delegate.h -FILE: ../../../flutter/shell/platform/windows/window.cc -FILE: ../../../flutter/shell/platform/windows/window.h FILE: ../../../flutter/shell/platform/windows/window_binding_handler.h FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 10369da9c097e..9d1ad9d03ce9c 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -96,8 +96,6 @@ source_set("flutter_windows_source") { "text_input_manager.h", "text_input_plugin.cc", "text_input_plugin.h", - "window.cc", - "window.h", "window_binding_handler.h", "window_binding_handler_delegate.h", "window_proc_delegate_manager.cc", diff --git a/shell/platform/windows/direct_manipulation.cc b/shell/platform/windows/direct_manipulation.cc index 72e676a1b3283..81c2fc01b789c 100644 --- a/shell/platform/windows/direct_manipulation.cc +++ b/shell/platform/windows/direct_manipulation.cc @@ -7,7 +7,7 @@ #include #include "flutter/shell/platform/windows/direct_manipulation.h" -#include "flutter/shell/platform/windows/window.h" +#include "flutter/shell/platform/windows/flutter_window.h" #include "flutter/shell/platform/windows/window_binding_handler_delegate.h" #define RETURN_IF_FAILED(operation) \ @@ -172,7 +172,7 @@ ULONG STDMETHODCALLTYPE DirectManipulationEventHandler::Release() { return 0; } -DirectManipulationOwner::DirectManipulationOwner(Window* window) +DirectManipulationOwner::DirectManipulationOwner(FlutterWindow* window) : window_(window) {} int DirectManipulationOwner::Init(unsigned int width, unsigned int height) { diff --git a/shell/platform/windows/direct_manipulation.h b/shell/platform/windows/direct_manipulation.h index 6270015fa6f4c..4d7046ab8ddd1 100644 --- a/shell/platform/windows/direct_manipulation.h +++ b/shell/platform/windows/direct_manipulation.h @@ -12,7 +12,7 @@ namespace flutter { -class Window; +class FlutterWindow; class WindowBindingHandlerDelegate; class DirectManipulationEventHandler; @@ -21,7 +21,7 @@ class DirectManipulationEventHandler; // DirectManipulation and WindowBindingHandlerDelegate. class DirectManipulationOwner { public: - explicit DirectManipulationOwner(Window* window); + explicit DirectManipulationOwner(FlutterWindow* window); virtual ~DirectManipulationOwner() = default; // Initialize a DirectManipulation viewport with specified width and height. // These should match the width and height of the application window. @@ -47,7 +47,7 @@ class DirectManipulationOwner { private: // The window gesture input is occuring on. - Window* window_; + FlutterWindow* window_; // Cookie needed to register child event handler with viewport. DWORD viewportHandlerCookie_; // Object needed for operation of the DirectManipulation API. diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index b8060a7a92dbb..710a966fc322a 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -12,8 +12,10 @@ #include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/dpi_utils.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/keyboard_utils.h" namespace flutter { @@ -23,6 +25,11 @@ namespace { // constant for machines running at 100% scaling. constexpr int base_dpi = 96; +static const int kMinTouchDeviceId = 0; +static const int kMaxTouchDeviceId = 128; + +static const int kLinesPerScrollWindowsDefault = 3; + // Maps a Flutter cursor name to an HCURSOR. // // Returns the arrow cursor for unknown constants. @@ -66,11 +73,78 @@ static HCURSOR GetCursorByName(const std::string& cursor_name) { return ::LoadCursor(nullptr, idc_name); } +static constexpr int32_t kDefaultPointerDeviceId = 0; + +// This method is only valid during a window message related to mouse/touch +// input. +// See +// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages?redirectedfrom=MSDN#distinguishing-pen-input-from-mouse-and-touch. +static FlutterPointerDeviceKind GetFlutterPointerDeviceKind() { + constexpr LPARAM kTouchOrPenSignature = 0xFF515700; + constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80; + constexpr LPARAM kSignatureMask = 0xFFFFFF00; + LPARAM info = GetMessageExtraInfo(); + if ((info & kSignatureMask) == kTouchOrPenSignature) { + if ((info & kTouchSignature) == kTouchSignature) { + return kFlutterPointerDeviceKindTouch; + } + return kFlutterPointerDeviceKindStylus; + } + return kFlutterPointerDeviceKindMouse; +} + +// Translates button codes from Win32 API to FlutterPointerMouseButtons. +static uint64_t ConvertWinButtonToFlutterButton(UINT button) { + switch (button) { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + return kFlutterPointerButtonMousePrimary; + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + return kFlutterPointerButtonMouseSecondary; + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + return kFlutterPointerButtonMouseMiddle; + case XBUTTON1: + return kFlutterPointerButtonMouseBack; + case XBUTTON2: + return kFlutterPointerButtonMouseForward; + } + FML_LOG(WARNING) << "Mouse button not recognized: " << button; + return 0; +} + } // namespace -FlutterWindow::FlutterWindow(int width, int height) - : binding_handler_delegate_(nullptr) { - Window::InitializeChild("FLUTTERVIEW", width, height); +FlutterWindow::FlutterWindow( + int width, + int height, + std::unique_ptr windows_proc_table, + std::unique_ptr text_input_manager) + : binding_handler_delegate_(nullptr), + touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId), + windows_proc_table_(std::move(windows_proc_table)), + text_input_manager_(std::move(text_input_manager)), + ax_fragment_root_(nullptr) { + // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is + // supported, |current_dpi_| should be updated in the + // kWmDpiChangedBeforeParent message. + current_dpi_ = GetDpiForHWND(nullptr); + + // Get initial value for wheel scroll lines + // TODO: Listen to changes for this value + // https://github.com/flutter/flutter/issues/107248 + UpdateScrollOffsetMultiplier(); + + if (windows_proc_table_ == nullptr) { + windows_proc_table_ = std::make_unique(); + } + if (text_input_manager_ == nullptr) { + text_input_manager_ = std::make_unique(); + } + keyboard_manager_ = std::make_unique(this); + + InitializeChild("FLUTTERVIEW", width, height); current_cursor_ = ::LoadCursor(nullptr, IDC_ARROW); } @@ -125,27 +199,6 @@ void FlutterWindow::OnWindowResized() { DwmFlush(); } -// Translates button codes from Win32 API to FlutterPointerMouseButtons. -static uint64_t ConvertWinButtonToFlutterButton(UINT button) { - switch (button) { - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - return kFlutterPointerButtonMousePrimary; - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - return kFlutterPointerButtonMouseSecondary; - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - return kFlutterPointerButtonMouseMiddle; - case XBUTTON1: - return kFlutterPointerButtonMouseBack; - case XBUTTON2: - return kFlutterPointerButtonMouseForward; - } - FML_LOG(WARNING) << "Mouse button not recognized: " << button; - return 0; -} - void FlutterWindow::OnDpiScale(unsigned int dpi){}; // When DesktopWindow notifies that a WM_Size message has come in @@ -359,4 +412,586 @@ void FlutterWindow::OnWindowStateEvent(WindowStateEvent event) { } } +void FlutterWindow::TrackMouseLeaveEvent(HWND hwnd) { + if (!tracking_mouse_leave_) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.hwndTrack = hwnd; + tme.dwFlags = TME_LEAVE; + TrackMouseEvent(&tme); + tracking_mouse_leave_ = true; + } +} + +void FlutterWindow::HandleResize(UINT width, UINT height) { + current_width_ = width; + current_height_ = height; + if (direct_manipulation_owner_) { + direct_manipulation_owner_->ResizeViewport(width, height); + } + OnResize(width, height); +} + +FlutterWindow* FlutterWindow::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void FlutterWindow::UpdateScrollOffsetMultiplier() { + UINT lines_per_scroll = kLinesPerScrollWindowsDefault; + + // Get lines per scroll wheel value from Windows + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_scroll, 0); + + // This logic is based off Chromium's implementation + // https://source.chromium.org/chromium/chromium/src/+/main:ui/events/blink/web_input_event_builders_win.cc;l=319-331 + scroll_offset_multiplier_ = + static_cast(lines_per_scroll) * 100.0 / 3.0; +} + +void FlutterWindow::InitializeChild(const char* title, + unsigned int width, + unsigned int height) { + Destroy(); + std::wstring converted_title = NarrowToWide(title); + + WNDCLASS window_class = RegisterWindowClass(converted_title); + + auto* result = CreateWindowEx( + 0, window_class.lpszClassName, converted_title.c_str(), + WS_CHILD | WS_VISIBLE, CW_DEFAULT, CW_DEFAULT, width, height, + HWND_MESSAGE, nullptr, window_class.hInstance, this); + + if (result == nullptr) { + auto error = GetLastError(); + LPWSTR message = nullptr; + size_t size = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, NULL); + OutputDebugString(message); + LocalFree(message); + } + SetUserObjectInformationA(GetCurrentProcess(), + UOI_TIMERPROC_EXCEPTION_SUPPRESSION, FALSE, 1); + // SetTimer is not precise, if a 16 ms interval is requested, it will instead + // often fire in an interval of 32 ms. Providing a value of 14 will ensure it + // runs every 16 ms, which will allow for 60 Hz trackpad gesture events, which + // is the maximal frequency supported by SetTimer. + SetTimer(result, kDirectManipulationTimer, 14, nullptr); + direct_manipulation_owner_ = std::make_unique(this); + direct_manipulation_owner_->Init(width, height); +} + +HWND FlutterWindow::GetWindowHandle() { + return window_handle_; +} + +BOOL FlutterWindow::Win32PeekMessage(LPMSG lpMsg, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg) { + return ::PeekMessage(lpMsg, window_handle_, wMsgFilterMin, wMsgFilterMax, + wRemoveMsg); +} + +uint32_t FlutterWindow::Win32MapVkToChar(uint32_t virtual_key) { + return ::MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); +} + +UINT FlutterWindow::Win32DispatchMessage(UINT Msg, + WPARAM wParam, + LPARAM lParam) { + return ::SendMessage(window_handle_, Msg, wParam, lParam); +} + +std::wstring FlutterWindow::NarrowToWide(const char* source) { + size_t length = strlen(source); + size_t outlen = 0; + std::wstring wideTitle(length, L'#'); + mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length); + return wideTitle; +} + +WNDCLASS FlutterWindow::RegisterWindowClass(std::wstring& title) { + window_class_name_ = title; + + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = title.c_str(); + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = nullptr; + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = WndProc; + RegisterClass(&window_class); + return window_class; +} + +LRESULT CALLBACK FlutterWindow::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto cs = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(cs->lpCreateParams)); + + auto that = static_cast(cs->lpCreateParams); + that->window_handle_ = window; + that->text_input_manager_->SetWindowHandle(window); + RegisterTouchWindow(window, 0); + } else if (FlutterWindow* that = GetThisFromHandle(window)) { + return that->HandleMessage(message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +FlutterWindow::HandleMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + LPARAM result_lparam = lparam; + int xPos = 0, yPos = 0; + UINT width = 0, height = 0; + UINT button_pressed = 0; + FlutterPointerDeviceKind device_kind; + + switch (message) { + case kWmDpiChangedBeforeParent: + current_dpi_ = GetDpiForHWND(window_handle_); + OnDpiScale(current_dpi_); + return 0; + case WM_SIZE: + width = LOWORD(lparam); + height = HIWORD(lparam); + + current_width_ = width; + current_height_ = height; + HandleResize(width, height); + + OnWindowStateEvent(width == 0 && height == 0 ? WindowStateEvent::kHide + : WindowStateEvent::kShow); + break; + case WM_PAINT: + OnPaint(); + break; + case WM_TOUCH: { + UINT num_points = LOWORD(wparam); + touch_points_.resize(num_points); + auto touch_input_handle = reinterpret_cast(lparam); + if (GetTouchInputInfo(touch_input_handle, num_points, + touch_points_.data(), sizeof(TOUCHINPUT))) { + for (const auto& touch : touch_points_) { + // Generate a mapped ID for the Windows-provided touch ID + auto touch_id = touch_id_generator_.GetGeneratedId(touch.dwID); + + POINT pt = {TOUCH_COORD_TO_PIXEL(touch.x), + TOUCH_COORD_TO_PIXEL(touch.y)}; + ScreenToClient(window_handle_, &pt); + auto x = static_cast(pt.x); + auto y = static_cast(pt.y); + + if (touch.dwFlags & TOUCHEVENTF_DOWN) { + OnPointerDown(x, y, kFlutterPointerDeviceKindTouch, touch_id, + WM_LBUTTONDOWN); + } else if (touch.dwFlags & TOUCHEVENTF_MOVE) { + OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id, 0); + } else if (touch.dwFlags & TOUCHEVENTF_UP) { + OnPointerUp(x, y, kFlutterPointerDeviceKindTouch, touch_id, + WM_LBUTTONDOWN); + OnPointerLeave(x, y, kFlutterPointerDeviceKindTouch, touch_id); + touch_id_generator_.ReleaseNumber(touch.dwID); + } + } + CloseTouchInputHandle(touch_input_handle); + } + return 0; + } + case WM_MOUSEMOVE: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind == kFlutterPointerDeviceKindMouse) { + TrackMouseLeaveEvent(window_handle_); + + xPos = GET_X_LPARAM(lparam); + yPos = GET_Y_LPARAM(lparam); + mouse_x_ = static_cast(xPos); + mouse_y_ = static_cast(yPos); + + int mods = 0; + if (wparam & MK_CONTROL) { + mods |= kControl; + } + if (wparam & MK_SHIFT) { + mods |= kShift; + } + OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId, + mods); + } + break; + case WM_MOUSELEAVE: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind == kFlutterPointerDeviceKindMouse) { + OnPointerLeave(mouse_x_, mouse_y_, device_kind, + kDefaultPointerDeviceId); + } + + // Once the tracked event is received, the TrackMouseEvent function + // resets. Set to false to make sure it's called once mouse movement is + // detected again. + tracking_mouse_leave_ = false; + break; + case WM_SETCURSOR: { + UINT hit_test_result = LOWORD(lparam); + if (hit_test_result == HTCLIENT) { + OnSetCursor(); + return TRUE; + } + break; + } + case WM_SETFOCUS: + OnWindowStateEvent(WindowStateEvent::kFocus); + ::CreateCaret(window_handle_, nullptr, 1, 1); + break; + case WM_KILLFOCUS: + OnWindowStateEvent(WindowStateEvent::kUnfocus); + ::DestroyCaret(); + break; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_XBUTTONDOWN: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind != kFlutterPointerDeviceKindMouse) { + break; + } + + if (message == WM_LBUTTONDOWN) { + // Capture the pointer in case the user drags outside the client area. + // In this case, the "mouse leave" event is delayed until the user + // releases the button. It's only activated on left click given that + // it's more common for apps to handle dragging with only the left + // button. + SetCapture(window_handle_); + } + button_pressed = message; + if (message == WM_XBUTTONDOWN) { + button_pressed = GET_XBUTTON_WPARAM(wparam); + } + xPos = GET_X_LPARAM(lparam); + yPos = GET_Y_LPARAM(lparam); + OnPointerDown(static_cast(xPos), static_cast(yPos), + device_kind, kDefaultPointerDeviceId, button_pressed); + break; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + device_kind = GetFlutterPointerDeviceKind(); + if (device_kind != kFlutterPointerDeviceKindMouse) { + break; + } + + if (message == WM_LBUTTONUP) { + ReleaseCapture(); + } + button_pressed = message; + if (message == WM_XBUTTONUP) { + button_pressed = GET_XBUTTON_WPARAM(wparam); + } + xPos = GET_X_LPARAM(lparam); + yPos = GET_Y_LPARAM(lparam); + OnPointerUp(static_cast(xPos), static_cast(yPos), + device_kind, kDefaultPointerDeviceId, button_pressed); + break; + case WM_MOUSEWHEEL: + OnScroll(0.0, + -(static_cast(HIWORD(wparam)) / + static_cast(WHEEL_DELTA)), + kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); + break; + case WM_MOUSEHWHEEL: + OnScroll((static_cast(HIWORD(wparam)) / + static_cast(WHEEL_DELTA)), + 0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); + break; + case WM_GETOBJECT: { + LRESULT lresult = OnGetObject(message, wparam, lparam); + if (lresult) { + return lresult; + } + break; + } + case WM_TIMER: + if (wparam == kDirectManipulationTimer) { + direct_manipulation_owner_->Update(); + return 0; + } + break; + case DM_POINTERHITTEST: { + if (direct_manipulation_owner_) { + UINT contact_id = GET_POINTERID_WPARAM(wparam); + POINTER_INPUT_TYPE pointer_type; + if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) && + pointer_type == PT_TOUCHPAD) { + direct_manipulation_owner_->SetContact(contact_id); + } + } + break; + } + case WM_INPUTLANGCHANGE: + // TODO(cbracken): pass this to TextInputManager to aid with + // language-specific issues. + break; + case WM_IME_SETCONTEXT: + OnImeSetContext(message, wparam, lparam); + // Strip the ISC_SHOWUICOMPOSITIONWINDOW bit from lparam before passing it + // to DefWindowProc() so that the composition window is hidden since + // Flutter renders the composing string itself. + result_lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + break; + case WM_IME_STARTCOMPOSITION: + OnImeStartComposition(message, wparam, lparam); + // Suppress further processing by DefWindowProc() so that the default + // system IME style isn't used, but rather the one set in the + // WM_IME_SETCONTEXT handler. + return TRUE; + case WM_IME_COMPOSITION: + OnImeComposition(message, wparam, lparam); + if (lparam & GCS_RESULTSTR || lparam & GCS_COMPSTR) { + // Suppress further processing by DefWindowProc() since otherwise it + // will emit the result string as WM_CHAR messages on commit. Instead, + // committing the composing text to the EditableText string is handled + // in TextInputModel::CommitComposing, triggered by + // OnImeEndComposition(). + return TRUE; + } + break; + case WM_IME_ENDCOMPOSITION: + OnImeEndComposition(message, wparam, lparam); + return TRUE; + case WM_IME_REQUEST: + OnImeRequest(message, wparam, lparam); + break; + case WM_UNICHAR: { + // Tell third-pary app, we can support Unicode. + if (wparam == UNICODE_NOCHAR) + return TRUE; + // DefWindowProc will send WM_CHAR for this WM_UNICHAR. + break; + } + case WM_THEMECHANGED: + OnThemeChange(); + break; + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + case WM_CHAR: + case WM_SYSCHAR: + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + if (keyboard_manager_->HandleMessage(message, wparam, lparam)) { + return 0; + } + break; + } + + return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); +} + +LRESULT FlutterWindow::OnGetObject(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + LRESULT reference_result = static_cast(0L); + + // Only the lower 32 bits of lparam are valid when checking the object id + // because it sometimes gets sign-extended incorrectly (but not always). + DWORD obj_id = static_cast(static_cast(lparam)); + + bool is_uia_request = static_cast(UiaRootObjectId) == obj_id; + bool is_msaa_request = static_cast(OBJID_CLIENT) == obj_id; + + if (is_uia_request || is_msaa_request) { + // On Windows, we don't get a notification that the screen reader has been + // enabled or disabled. There is an API to query for screen reader state, + // but that state isn't set by all screen readers, including by Narrator, + // the screen reader that ships with Windows: + // https://docs.microsoft.com/en-us/windows/win32/winauto/screen-reader-parameter + // + // Instead, we enable semantics in Flutter if Windows issues queries for + // Microsoft Active Accessibility (MSAA) COM objects. + OnUpdateSemanticsEnabled(true); + } + + gfx::NativeViewAccessible root_view = GetNativeViewAccessible(); + // TODO(schectman): UIA is currently disabled by default. + // https://github.com/flutter/flutter/issues/114547 + if (root_view) { + CreateAxFragmentRoot(); + if (is_uia_request) { +#ifdef FLUTTER_ENGINE_USE_UIA + // Retrieve UIA object for the root view. + Microsoft::WRL::ComPtr root; + if (SUCCEEDED( + ax_fragment_root_->GetNativeViewAccessible()->QueryInterface( + IID_PPV_ARGS(&root)))) { + // Return the UIA object via UiaReturnRawElementProvider(). See: + // https://docs.microsoft.com/en-us/windows/win32/winauto/wm-getobject + reference_result = UiaReturnRawElementProvider(window_handle_, wparam, + lparam, root.Get()); + } else { + FML_LOG(ERROR) << "Failed to query AX fragment root."; + } +#endif // FLUTTER_ENGINE_USE_UIA + } else if (is_msaa_request) { + // Create the accessibility root if it does not already exist. + // Return the IAccessible for the root view. + Microsoft::WRL::ComPtr root; + ax_fragment_root_->GetNativeViewAccessible()->QueryInterface( + IID_PPV_ARGS(&root)); + reference_result = LresultFromObject(IID_IAccessible, wparam, root.Get()); + } + } + return reference_result; +} + +void FlutterWindow::OnImeSetContext(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + if (wparam != 0) { + text_input_manager_->CreateImeWindow(); + } +} + +void FlutterWindow::OnImeStartComposition(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + text_input_manager_->CreateImeWindow(); + OnComposeBegin(); +} + +void FlutterWindow::OnImeComposition(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + // Update the IME window position. + text_input_manager_->UpdateImeWindow(); + + if (lparam == 0) { + OnComposeChange(u"", 0); + OnComposeCommit(); + } + + // Process GCS_RESULTSTR at fisrt, because Google Japanese Input and ATOK send + // both GCS_RESULTSTR and GCS_COMPSTR to commit composed text and send new + // composing text. + if (lparam & GCS_RESULTSTR) { + // Commit but don't end composing. + // Read the committed composing string. + long pos = text_input_manager_->GetComposingCursorPosition(); + std::optional text = text_input_manager_->GetResultString(); + if (text) { + OnComposeChange(text.value(), pos); + OnComposeCommit(); + } + } + if (lparam & GCS_COMPSTR) { + // Read the in-progress composing string. + long pos = text_input_manager_->GetComposingCursorPosition(); + std::optional text = + text_input_manager_->GetComposingString(); + if (text) { + OnComposeChange(text.value(), pos); + } + } +} + +void FlutterWindow::OnImeEndComposition(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + text_input_manager_->DestroyImeWindow(); + OnComposeEnd(); +} + +void FlutterWindow::OnImeRequest(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + // TODO(cbracken): Handle IMR_RECONVERTSTRING, IMR_DOCUMENTFEED, + // and IMR_QUERYCHARPOSITION messages. + // https://github.com/flutter/flutter/issues/74547 +} + +void FlutterWindow::AbortImeComposing() { + text_input_manager_->AbortComposing(); +} + +void FlutterWindow::UpdateCursorRect(const Rect& rect) { + text_input_manager_->UpdateCaretRect(rect); +} + +UINT FlutterWindow::GetCurrentDPI() { + return current_dpi_; +} + +UINT FlutterWindow::GetCurrentWidth() { + return current_width_; +} + +UINT FlutterWindow::GetCurrentHeight() { + return current_height_; +} + +float FlutterWindow::GetScrollOffsetMultiplier() { + return scroll_offset_multiplier_; +} + +bool FlutterWindow::GetHighContrastEnabled() { + HIGHCONTRAST high_contrast = {.cbSize = sizeof(HIGHCONTRAST)}; + // API call is only supported on Windows 8+ + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), + &high_contrast, 0)) { + return high_contrast.dwFlags & HCF_HIGHCONTRASTON; + } else { + FML_LOG(INFO) << "Failed to get status of high contrast feature," + << "support only for Windows 8 + "; + return false; + } +} + +LRESULT FlutterWindow::Win32DefWindowProc(HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam) { + return ::DefWindowProc(hWnd, Msg, wParam, lParam); +} + +void FlutterWindow::Destroy() { + if (window_handle_) { + text_input_manager_->SetWindowHandle(nullptr); + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + + UnregisterClass(window_class_name_.c_str(), nullptr); +} + +void FlutterWindow::CreateAxFragmentRoot() { + if (ax_fragment_root_) { + return; + } + ax_fragment_root_ = std::make_unique( + window_handle_, GetAxFragmentRootDelegate()); + alert_delegate_ = + std::make_unique(*ax_fragment_root_); + ui::AXPlatformNode* alert_node = + ui::AXPlatformNodeWin::Create(alert_delegate_.get()); + alert_node_.reset(static_cast(alert_node)); + ax_fragment_root_->SetAlertNode(alert_node_.get()); +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_window.h b/shell/platform/windows/flutter_window.h index 2b242315acc89..f1347369a3517 100644 --- a/shell/platform/windows/flutter_window.h +++ b/shell/platform/windows/flutter_window.h @@ -9,11 +9,22 @@ #include #include "flutter/fml/macros.h" +#include "flutter/shell/platform/common/alert_platform_node_delegate.h" #include "flutter/shell/platform/common/geometry.h" #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/direct_manipulation.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" -#include "flutter/shell/platform/windows/window.h" +#include "flutter/shell/platform/windows/keyboard_manager.h" +#include "flutter/shell/platform/windows/sequential_id_generator.h" +#include "flutter/shell/platform/windows/text_input_manager.h" #include "flutter/shell/platform/windows/window_binding_handler.h" +#include "flutter/shell/platform/windows/windows_lifecycle_manager.h" +#include "flutter/shell/platform/windows/windows_proc_table.h" +#include "flutter/shell/platform/windows/windowsx_shim.h" +#include "flutter/third_party/accessibility/ax/platform/ax_fragment_root_delegate_win.h" +#include "flutter/third_party/accessibility/ax/platform/ax_fragment_root_win.h" +#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h" +#include "flutter/third_party/accessibility/gfx/native_widget_types.h" namespace flutter { @@ -21,151 +32,310 @@ namespace flutter { // the future, there will likely be a CoreWindow-based FlutterWindow as well. // At the point may make sense to dependency inject the native window rather // than inherit. -class FlutterWindow : public Window, public WindowBindingHandler { +class FlutterWindow : public KeyboardManager::WindowDelegate, + public WindowBindingHandler { public: // Create flutter Window for use as child window - FlutterWindow(int width, int height); + FlutterWindow(int width, + int height, + std::unique_ptr windows_proc_table = nullptr, + std::unique_ptr text_input_manager = nullptr); virtual ~FlutterWindow(); - // |Window| - void OnDpiScale(unsigned int dpi) override; - - // |Window| - void OnResize(unsigned int width, unsigned int height) override; - - // |Window| - void OnPaint() override; - - // |Window| - void OnPointerMove(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id, - int modifiers_state) override; - - // |Window| - void OnPointerDown(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id, - UINT button) override; - - // |Window| - void OnPointerUp(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id, - UINT button) override; - - // |Window| - void OnPointerLeave(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id) override; - - // |Window| - void OnSetCursor() override; - - // |Window| - void OnText(const std::u16string& text) override; - - // |Window| - void OnKey(int key, - int scancode, - int action, - char32_t character, - bool extended, - bool was_down, - KeyEventCallback callback) override; - - // |Window| - void OnComposeBegin() override; - - // |Window| - void OnComposeCommit() override; - - // |Window| - void OnComposeEnd() override; - - // |Window| - void OnComposeChange(const std::u16string& text, int cursor_pos) override; + // Initializes as a child window with size using |width| and |height| and + // |title| to identify the windowclass. Does not show window, window must be + // parented into window hierarchy by caller. + void InitializeChild(const char* title, + unsigned int width, + unsigned int height); + + HWND GetWindowHandle(); + + // |KeyboardManager::WindowDelegate| + virtual BOOL Win32PeekMessage(LPMSG lpMsg, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg) override; + + // |KeyboardManager::WindowDelegate| + virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) override; + + // |KeyboardManager::WindowDelegate| + virtual UINT Win32DispatchMessage(UINT Msg, + WPARAM wParam, + LPARAM lParam) override; + + // Called when the DPI changes either when a + // user drags the window between monitors of differing DPI or when the user + // manually changes the scale factor. + virtual void OnDpiScale(unsigned int dpi); + + // Called when a resize occurs. + virtual void OnResize(unsigned int width, unsigned int height); + + // Called when a paint is requested. + virtual void OnPaint(); + + // Called when the pointer moves within the + // window bounds. + virtual void OnPointerMove(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + int modifiers_state); + + // Called when the a mouse button, determined by |button|, goes down. + virtual void OnPointerDown(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button); + + // Called when the a mouse button, determined by |button|, goes from + // down to up + virtual void OnPointerUp(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id, + UINT button); + + // Called when the mouse leaves the window. + virtual void OnPointerLeave(double x, + double y, + FlutterPointerDeviceKind device_kind, + int32_t device_id); + + // Called when the cursor should be set for the client area. + virtual void OnSetCursor(); + + // |WindowBindingHandlerDelegate| + virtual void OnText(const std::u16string& text) override; + + // |WindowBindingHandlerDelegate| + virtual void OnKey(int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + KeyEventCallback callback) override; + + // Called when IME composing begins. + virtual void OnComposeBegin(); + + // Called when IME composing text is committed. + virtual void OnComposeCommit(); + + // Called when IME composing ends. + virtual void OnComposeEnd(); + + // Called when IME composing text or cursor position changes. + virtual void OnComposeChange(const std::u16string& text, int cursor_pos); // |FlutterWindowBindingHandler| - void OnCursorRectUpdated(const Rect& rect) override; + virtual void OnCursorRectUpdated(const Rect& rect) override; // |FlutterWindowBindingHandler| - void OnResetImeComposing() override; + virtual void OnResetImeComposing() override; - // |Window| - void OnUpdateSemanticsEnabled(bool enabled) override; + // Called when accessibility support is enabled or disabled. + virtual void OnUpdateSemanticsEnabled(bool enabled); - // |Window| - void OnScroll(double delta_x, - double delta_y, - FlutterPointerDeviceKind device_kind, - int32_t device_id) override; + // Called when mouse scrollwheel input occurs. + virtual void OnScroll(double delta_x, + double delta_y, + FlutterPointerDeviceKind device_kind, + int32_t device_id); - // |Window| - gfx::NativeViewAccessible GetNativeViewAccessible() override; + // Returns the root view accessibility node, or nullptr if none. + virtual gfx::NativeViewAccessible GetNativeViewAccessible(); // |FlutterWindowBindingHandler| - void SetView(WindowBindingHandlerDelegate* view) override; + virtual void SetView(WindowBindingHandlerDelegate* view) override; // |FlutterWindowBindingHandler| - WindowsRenderTarget GetRenderTarget() override; + virtual WindowsRenderTarget GetRenderTarget() override; // |FlutterWindowBindingHandler| - PlatformWindow GetPlatformWindow() override; + virtual PlatformWindow GetPlatformWindow() override; // |FlutterWindowBindingHandler| - float GetDpiScale() override; + virtual float GetDpiScale() override; // |FlutterWindowBindingHandler| - bool IsVisible() override; + virtual bool IsVisible() override; // |FlutterWindowBindingHandler| - PhysicalWindowBounds GetPhysicalWindowBounds() override; + virtual PhysicalWindowBounds GetPhysicalWindowBounds() override; // |FlutterWindowBindingHandler| - void UpdateFlutterCursor(const std::string& cursor_name) override; + virtual void UpdateFlutterCursor(const std::string& cursor_name) override; // |FlutterWindowBindingHandler| - void SetFlutterCursor(HCURSOR cursor) override; + virtual void SetFlutterCursor(HCURSOR cursor) override; // |FlutterWindowBindingHandler| - void OnWindowResized() override; + virtual void OnWindowResized() override; // |FlutterWindowBindingHandler| - bool OnBitmapSurfaceUpdated(const void* allocation, - size_t row_bytes, - size_t height) override; + virtual bool OnBitmapSurfaceUpdated(const void* allocation, + size_t row_bytes, + size_t height) override; // |FlutterWindowBindingHandler| - PointerLocation GetPrimaryPointerLocation() override; + virtual PointerLocation GetPrimaryPointerLocation() override; - // |Window| - void OnThemeChange() override; + // Called when a theme change message is issued. + virtual void OnThemeChange(); // |WindowBindingHandler| - void SendInitialAccessibilityFeatures() override; + virtual void SendInitialAccessibilityFeatures() override; // |WindowBindingHandler| - AlertPlatformNodeDelegate* GetAlertDelegate() override; + virtual AlertPlatformNodeDelegate* GetAlertDelegate() override; // |WindowBindingHandler| - ui::AXPlatformNodeWin* GetAlert() override; + virtual ui::AXPlatformNodeWin* GetAlert() override; // |WindowBindingHandler| - bool NeedsVSync() override; + virtual bool NeedsVSync() override; + + // Called to obtain a pointer to the fragment root delegate. + virtual ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate(); + + // Called on a resize or focus event. + virtual void OnWindowStateEvent(WindowStateEvent event); + + protected: + // Win32's DefWindowProc. + // + // Used as the fallback behavior of HandleMessage. Exposed for dependency + // injection. + virtual LRESULT Win32DefWindowProc(HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam); + + // Converts a c string to a wide unicode string. + std::wstring NarrowToWide(const char* source); + + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + LRESULT HandleMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when the OS requests a COM object. + // + // The primary use of this function is to supply Windows with wrapped + // semantics objects for use by Windows accessibility. + virtual LRESULT OnGetObject(UINT const message, + WPARAM const wparam, + LPARAM const lparam); + + // Called when a window is activated in order to configure IME support for + // multi-step text input. + virtual void OnImeSetContext(UINT const message, + WPARAM const wparam, + LPARAM const lparam); + + // Called when multi-step text input begins when using an IME. + virtual void OnImeStartComposition(UINT const message, + WPARAM const wparam, + LPARAM const lparam); + + // Called when edits/commit of multi-step text input occurs when using an IME. + virtual void OnImeComposition(UINT const message, + WPARAM const wparam, + LPARAM const lparam); + + // Called when multi-step text input ends when using an IME. + virtual void OnImeEndComposition(UINT const message, + WPARAM const wparam, + LPARAM const lparam); + + // Called when the user triggers an IME-specific request such as input + // reconversion, where an existing input sequence is returned to composing + // mode to select an alternative candidate conversion. + virtual void OnImeRequest(UINT const message, + WPARAM const wparam, + LPARAM const lparam); + + // Called when the app ends IME composing, such as when the text input client + // is cleared or changed. + virtual void AbortImeComposing(); + + // Called when the cursor rect has been updated. + // + // |rect| is in Win32 window coordinates. + virtual void UpdateCursorRect(const Rect& rect); + + UINT GetCurrentDPI(); + + UINT GetCurrentWidth(); + + UINT GetCurrentHeight(); + + // Returns the current pixel per scroll tick value. + virtual float GetScrollOffsetMultiplier(); + + // Check if the high contrast feature is enabled on the OS + virtual bool GetHighContrastEnabled(); + + // Delegate to a alert_node_ used to set the announcement text. + std::unique_ptr alert_delegate_; + + // Accessibility node that represents an alert. + std::unique_ptr alert_node_; + + // Handles running DirectManipulation on the window to receive trackpad + // gestures. + std::unique_ptr direct_manipulation_owner_; - // |Window| - ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() override; + private: + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; - // |Window| - virtual void OnWindowStateEvent(WindowStateEvent event) override; + // WM_DPICHANGED_BEFOREPARENT defined in more recent Windows + // SDK + static const long kWmDpiChangedBeforeParent = 0x02E2; + + // Timer identifier for DirectManipulation gesture polling. + static const int kDirectManipulationTimer = 1; + + // Release OS resources associated with the window. + void Destroy(); + + // Registers a window class with default style attributes, cursor and + // icon. + WNDCLASS RegisterWindowClass(std::wstring& title); + + // Retrieves a class instance pointer for |window| + static FlutterWindow* GetThisFromHandle(HWND const window) noexcept; + + // Activates tracking for a "mouse leave" event. + void TrackMouseLeaveEvent(HWND hwnd); + + // Stores new width and height and calls |OnResize| to notify inheritors + void HandleResize(UINT width, UINT height); + + // Updates the cached scroll_offset_multiplier_ value based off OS settings. + void UpdateScrollOffsetMultiplier(); + + // Creates the ax_fragment_root_, alert_delegate_ and alert_node_ if they do + // not yet exist. + // Once set, they are not reset to nullptr. + void CreateAxFragmentRoot(); - private: // A pointer to a FlutterWindowsView that can be used to update engine // windowing and input state. WindowBindingHandlerDelegate* binding_handler_delegate_; @@ -182,6 +352,52 @@ class FlutterWindow : public Window, public WindowBindingHandler { bool restored_ = false; bool focused_ = false; + int current_dpi_ = 0; + int current_width_ = 0; + int current_height_ = 0; + + // Holds the conversion factor from lines scrolled to pixels scrolled. + float scroll_offset_multiplier_; + + // Member variable to hold window handle. + HWND window_handle_ = nullptr; + + // Member variable to hold the window title. + std::wstring window_class_name_; + + // Set to true to be notified when the mouse leaves the window. + bool tracking_mouse_leave_ = false; + + // Keeps track of the last key code produced by a WM_KEYDOWN or WM_SYSKEYDOWN + // message. + int keycode_for_char_message_ = 0; + + // Keeps track of the last mouse coordinates by a WM_MOUSEMOVE message. + double mouse_x_ = 0; + double mouse_y_ = 0; + + // Generates touch point IDs for touch events. + SequentialIdGenerator touch_id_generator_; + + // Abstracts Windows APIs that may not be available on all supported versions + // of Windows. + std::unique_ptr windows_proc_table_; + + // Manages IME state. + std::unique_ptr text_input_manager_; + + // Manages IME state. + std::unique_ptr keyboard_manager_; + + // Used for temporarily storing the WM_TOUCH-provided touch points. + std::vector touch_points_; + + // Implements IRawElementProviderFragmentRoot when UIA is enabled. + std::unique_ptr ax_fragment_root_; + + // Allow WindowAXFragmentRootDelegate to access protected method. + friend class WindowAXFragmentRootDelegate; + FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindow); }; diff --git a/shell/platform/windows/testing/mock_direct_manipulation.h b/shell/platform/windows/testing/mock_direct_manipulation.h index ecf1f7bea9a9a..fc984cbf34fc3 100644 --- a/shell/platform/windows/testing/mock_direct_manipulation.h +++ b/shell/platform/windows/testing/mock_direct_manipulation.h @@ -15,7 +15,7 @@ namespace testing { /// Mock for the |DirectManipulationOwner| base class. class MockDirectManipulationOwner : public DirectManipulationOwner { public: - explicit MockDirectManipulationOwner(Window* window) + explicit MockDirectManipulationOwner(FlutterWindow* window) : DirectManipulationOwner(window){}; virtual ~MockDirectManipulationOwner() = default; diff --git a/shell/platform/windows/testing/mock_window.cc b/shell/platform/windows/testing/mock_window.cc index 9021aecb3352a..d6f8db8988772 100644 --- a/shell/platform/windows/testing/mock_window.cc +++ b/shell/platform/windows/testing/mock_window.cc @@ -6,10 +6,13 @@ namespace flutter { namespace testing { -MockWindow::MockWindow() : Window(){}; +MockWindow::MockWindow() : FlutterWindow(1, 1){}; MockWindow::MockWindow(std::unique_ptr window_proc_table, std::unique_ptr text_input_manager) - : Window(std::move(window_proc_table), std::move(text_input_manager)){}; + : FlutterWindow(1, + 1, + std::move(window_proc_table), + std::move(text_input_manager)){}; MockWindow::~MockWindow() = default; @@ -49,7 +52,7 @@ void MockWindow::InjectMessageList(int count, const Win32Message* messages) { void MockWindow::CallOnImeComposition(UINT const message, WPARAM const wparam, LPARAM const lparam) { - Window::OnImeComposition(message, wparam, lparam); + FlutterWindow::OnImeComposition(message, wparam, lparam); } } // namespace testing diff --git a/shell/platform/windows/testing/mock_window.h b/shell/platform/windows/testing/mock_window.h index b363824a08e4b..a2d9541302c6c 100644 --- a/shell/platform/windows/testing/mock_window.h +++ b/shell/platform/windows/testing/mock_window.h @@ -6,15 +6,15 @@ #define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WIN32_WINDOW_H_ #include "flutter/fml/macros.h" +#include "flutter/shell/platform/windows/flutter_window.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" -#include "flutter/shell/platform/windows/window.h" #include "gmock/gmock.h" namespace flutter { namespace testing { -/// Mock for the |Window| base class. -class MockWindow : public Window { +/// Mock for the |FlutterWindow| base class. +class MockWindow : public FlutterWindow { public: MockWindow(); MockWindow(std::unique_ptr windows_proc_table, diff --git a/shell/platform/windows/window.cc b/shell/platform/windows/window.cc deleted file mode 100644 index 646a2ec9c84a6..0000000000000 --- a/shell/platform/windows/window.cc +++ /dev/null @@ -1,689 +0,0 @@ -// 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 "flutter/shell/platform/windows/window.h" - -#include "base/win/atl.h" // NOLINT(build/include_order) - -#include -#include -#include -#include -#include - -#include - -#include "flutter/shell/platform/common/flutter_platform_node_delegate.h" -#include "flutter/shell/platform/windows/dpi_utils.h" -#include "flutter/shell/platform/windows/keyboard_utils.h" - -namespace flutter { - -namespace { - -static constexpr int32_t kDefaultPointerDeviceId = 0; - -// This method is only valid during a window message related to mouse/touch -// input. -// See -// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages?redirectedfrom=MSDN#distinguishing-pen-input-from-mouse-and-touch. -static FlutterPointerDeviceKind GetFlutterPointerDeviceKind() { - constexpr LPARAM kTouchOrPenSignature = 0xFF515700; - constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80; - constexpr LPARAM kSignatureMask = 0xFFFFFF00; - LPARAM info = GetMessageExtraInfo(); - if ((info & kSignatureMask) == kTouchOrPenSignature) { - if ((info & kTouchSignature) == kTouchSignature) { - return kFlutterPointerDeviceKindTouch; - } - return kFlutterPointerDeviceKindStylus; - } - return kFlutterPointerDeviceKindMouse; -} - -char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) { - return 0x10000 + ((static_cast(high) & 0x000003FF) << 10) + - (low & 0x3FF); -} - -static const int kMinTouchDeviceId = 0; -static const int kMaxTouchDeviceId = 128; - -static const int kLinesPerScrollWindowsDefault = 3; - -} // namespace - -Window::Window() : Window(nullptr, nullptr) {} - -Window::Window(std::unique_ptr windows_proc_table, - std::unique_ptr text_input_manager) - : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId), - windows_proc_table_(std::move(windows_proc_table)), - text_input_manager_(std::move(text_input_manager)), - ax_fragment_root_(nullptr) { - // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is - // supported, |current_dpi_| should be updated in the - // kWmDpiChangedBeforeParent message. - current_dpi_ = GetDpiForHWND(nullptr); - - // Get initial value for wheel scroll lines - // TODO: Listen to changes for this value - // https://github.com/flutter/flutter/issues/107248 - UpdateScrollOffsetMultiplier(); - - if (windows_proc_table_ == nullptr) { - windows_proc_table_ = std::make_unique(); - } - if (text_input_manager_ == nullptr) { - text_input_manager_ = std::make_unique(); - } - keyboard_manager_ = std::make_unique(this); -} - -Window::~Window() {} - -void Window::InitializeChild(const char* title, - unsigned int width, - unsigned int height) { - Destroy(); - std::wstring converted_title = NarrowToWide(title); - - WNDCLASS window_class = RegisterWindowClass(converted_title); - - auto* result = CreateWindowEx( - 0, window_class.lpszClassName, converted_title.c_str(), - WS_CHILD | WS_VISIBLE, CW_DEFAULT, CW_DEFAULT, width, height, - HWND_MESSAGE, nullptr, window_class.hInstance, this); - - if (result == nullptr) { - auto error = GetLastError(); - LPWSTR message = nullptr; - size_t size = FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message), 0, NULL); - OutputDebugString(message); - LocalFree(message); - } - SetUserObjectInformationA(GetCurrentProcess(), - UOI_TIMERPROC_EXCEPTION_SUPPRESSION, FALSE, 1); - // SetTimer is not precise, if a 16 ms interval is requested, it will instead - // often fire in an interval of 32 ms. Providing a value of 14 will ensure it - // runs every 16 ms, which will allow for 60 Hz trackpad gesture events, which - // is the maximal frequency supported by SetTimer. - SetTimer(result, kDirectManipulationTimer, 14, nullptr); - direct_manipulation_owner_ = std::make_unique(this); - direct_manipulation_owner_->Init(width, height); -} - -std::wstring Window::NarrowToWide(const char* source) { - size_t length = strlen(source); - size_t outlen = 0; - std::wstring wideTitle(length, L'#'); - mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length); - return wideTitle; -} - -WNDCLASS Window::RegisterWindowClass(std::wstring& title) { - window_class_name_ = title; - - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = title.c_str(); - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = nullptr; - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = WndProc; - RegisterClass(&window_class); - return window_class; -} - -LRESULT CALLBACK Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto cs = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(cs->lpCreateParams)); - - auto that = static_cast(cs->lpCreateParams); - that->window_handle_ = window; - that->text_input_manager_->SetWindowHandle(window); - RegisterTouchWindow(window, 0); - } else if (Window* that = GetThisFromHandle(window)) { - return that->HandleMessage(message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -void Window::TrackMouseLeaveEvent(HWND hwnd) { - if (!tracking_mouse_leave_) { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.hwndTrack = hwnd; - tme.dwFlags = TME_LEAVE; - TrackMouseEvent(&tme); - tracking_mouse_leave_ = true; - } -} - -LRESULT Window::OnGetObject(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - LRESULT reference_result = static_cast(0L); - - // Only the lower 32 bits of lparam are valid when checking the object id - // because it sometimes gets sign-extended incorrectly (but not always). - DWORD obj_id = static_cast(static_cast(lparam)); - - bool is_uia_request = static_cast(UiaRootObjectId) == obj_id; - bool is_msaa_request = static_cast(OBJID_CLIENT) == obj_id; - - if (is_uia_request || is_msaa_request) { - // On Windows, we don't get a notification that the screen reader has been - // enabled or disabled. There is an API to query for screen reader state, - // but that state isn't set by all screen readers, including by Narrator, - // the screen reader that ships with Windows: - // https://docs.microsoft.com/en-us/windows/win32/winauto/screen-reader-parameter - // - // Instead, we enable semantics in Flutter if Windows issues queries for - // Microsoft Active Accessibility (MSAA) COM objects. - OnUpdateSemanticsEnabled(true); - } - - gfx::NativeViewAccessible root_view = GetNativeViewAccessible(); - // TODO(schectman): UIA is currently disabled by default. - // https://github.com/flutter/flutter/issues/114547 - if (root_view) { - CreateAxFragmentRoot(); - if (is_uia_request) { -#ifdef FLUTTER_ENGINE_USE_UIA - // Retrieve UIA object for the root view. - Microsoft::WRL::ComPtr root; - if (SUCCEEDED( - ax_fragment_root_->GetNativeViewAccessible()->QueryInterface( - IID_PPV_ARGS(&root)))) { - // Return the UIA object via UiaReturnRawElementProvider(). See: - // https://docs.microsoft.com/en-us/windows/win32/winauto/wm-getobject - reference_result = UiaReturnRawElementProvider(window_handle_, wparam, - lparam, root.Get()); - } else { - FML_LOG(ERROR) << "Failed to query AX fragment root."; - } -#endif // FLUTTER_ENGINE_USE_UIA - } else if (is_msaa_request) { - // Create the accessibility root if it does not already exist. - // Return the IAccessible for the root view. - Microsoft::WRL::ComPtr root; - ax_fragment_root_->GetNativeViewAccessible()->QueryInterface( - IID_PPV_ARGS(&root)); - reference_result = LresultFromObject(IID_IAccessible, wparam, root.Get()); - } - } - return reference_result; -} - -void Window::OnImeSetContext(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - if (wparam != 0) { - text_input_manager_->CreateImeWindow(); - } -} - -void Window::OnImeStartComposition(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - text_input_manager_->CreateImeWindow(); - OnComposeBegin(); -} - -void Window::OnImeComposition(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - // Update the IME window position. - text_input_manager_->UpdateImeWindow(); - - if (lparam == 0) { - OnComposeChange(u"", 0); - OnComposeCommit(); - } - - // Process GCS_RESULTSTR at fisrt, because Google Japanese Input and ATOK send - // both GCS_RESULTSTR and GCS_COMPSTR to commit composed text and send new - // composing text. - if (lparam & GCS_RESULTSTR) { - // Commit but don't end composing. - // Read the committed composing string. - long pos = text_input_manager_->GetComposingCursorPosition(); - std::optional text = text_input_manager_->GetResultString(); - if (text) { - OnComposeChange(text.value(), pos); - OnComposeCommit(); - } - } - if (lparam & GCS_COMPSTR) { - // Read the in-progress composing string. - long pos = text_input_manager_->GetComposingCursorPosition(); - std::optional text = - text_input_manager_->GetComposingString(); - if (text) { - OnComposeChange(text.value(), pos); - } - } -} - -void Window::OnImeEndComposition(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - text_input_manager_->DestroyImeWindow(); - OnComposeEnd(); -} - -void Window::OnImeRequest(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - // TODO(cbracken): Handle IMR_RECONVERTSTRING, IMR_DOCUMENTFEED, - // and IMR_QUERYCHARPOSITION messages. - // https://github.com/flutter/flutter/issues/74547 -} - -void Window::AbortImeComposing() { - text_input_manager_->AbortComposing(); -} - -void Window::UpdateCursorRect(const Rect& rect) { - text_input_manager_->UpdateCaretRect(rect); -} - -static uint16_t ResolveKeyCode(uint16_t original, - bool extended, - uint8_t scancode) { - switch (original) { - case VK_SHIFT: - case VK_LSHIFT: - return MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX); - case VK_MENU: - case VK_LMENU: - return extended ? VK_RMENU : VK_LMENU; - case VK_CONTROL: - case VK_LCONTROL: - return extended ? VK_RCONTROL : VK_LCONTROL; - default: - return original; - } -} - -static bool IsPrintable(uint32_t c) { - constexpr char32_t kMinPrintable = ' '; - constexpr char32_t kDelete = 0x7F; - return c >= kMinPrintable && c != kDelete; -} - -LRESULT -Window::HandleMessage(UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - LPARAM result_lparam = lparam; - int xPos = 0, yPos = 0; - UINT width = 0, height = 0; - UINT button_pressed = 0; - FlutterPointerDeviceKind device_kind; - - switch (message) { - case kWmDpiChangedBeforeParent: - current_dpi_ = GetDpiForHWND(window_handle_); - OnDpiScale(current_dpi_); - return 0; - case WM_SIZE: - width = LOWORD(lparam); - height = HIWORD(lparam); - - current_width_ = width; - current_height_ = height; - HandleResize(width, height); - - OnWindowStateEvent(width == 0 && height == 0 ? WindowStateEvent::kHide - : WindowStateEvent::kShow); - break; - case WM_PAINT: - OnPaint(); - break; - case WM_TOUCH: { - UINT num_points = LOWORD(wparam); - touch_points_.resize(num_points); - auto touch_input_handle = reinterpret_cast(lparam); - if (GetTouchInputInfo(touch_input_handle, num_points, - touch_points_.data(), sizeof(TOUCHINPUT))) { - for (const auto& touch : touch_points_) { - // Generate a mapped ID for the Windows-provided touch ID - auto touch_id = touch_id_generator_.GetGeneratedId(touch.dwID); - - POINT pt = {TOUCH_COORD_TO_PIXEL(touch.x), - TOUCH_COORD_TO_PIXEL(touch.y)}; - ScreenToClient(window_handle_, &pt); - auto x = static_cast(pt.x); - auto y = static_cast(pt.y); - - if (touch.dwFlags & TOUCHEVENTF_DOWN) { - OnPointerDown(x, y, kFlutterPointerDeviceKindTouch, touch_id, - WM_LBUTTONDOWN); - } else if (touch.dwFlags & TOUCHEVENTF_MOVE) { - OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id, 0); - } else if (touch.dwFlags & TOUCHEVENTF_UP) { - OnPointerUp(x, y, kFlutterPointerDeviceKindTouch, touch_id, - WM_LBUTTONDOWN); - OnPointerLeave(x, y, kFlutterPointerDeviceKindTouch, touch_id); - touch_id_generator_.ReleaseNumber(touch.dwID); - } - } - CloseTouchInputHandle(touch_input_handle); - } - return 0; - } - case WM_MOUSEMOVE: - device_kind = GetFlutterPointerDeviceKind(); - if (device_kind == kFlutterPointerDeviceKindMouse) { - TrackMouseLeaveEvent(window_handle_); - - xPos = GET_X_LPARAM(lparam); - yPos = GET_Y_LPARAM(lparam); - mouse_x_ = static_cast(xPos); - mouse_y_ = static_cast(yPos); - - int mods = 0; - if (wparam & MK_CONTROL) { - mods |= kControl; - } - if (wparam & MK_SHIFT) { - mods |= kShift; - } - OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId, - mods); - } - break; - case WM_MOUSELEAVE: - device_kind = GetFlutterPointerDeviceKind(); - if (device_kind == kFlutterPointerDeviceKindMouse) { - OnPointerLeave(mouse_x_, mouse_y_, device_kind, - kDefaultPointerDeviceId); - } - - // Once the tracked event is received, the TrackMouseEvent function - // resets. Set to false to make sure it's called once mouse movement is - // detected again. - tracking_mouse_leave_ = false; - break; - case WM_SETCURSOR: { - UINT hit_test_result = LOWORD(lparam); - if (hit_test_result == HTCLIENT) { - OnSetCursor(); - return TRUE; - } - break; - } - case WM_SETFOCUS: - OnWindowStateEvent(WindowStateEvent::kFocus); - ::CreateCaret(window_handle_, nullptr, 1, 1); - break; - case WM_KILLFOCUS: - OnWindowStateEvent(WindowStateEvent::kUnfocus); - ::DestroyCaret(); - break; - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_XBUTTONDOWN: - device_kind = GetFlutterPointerDeviceKind(); - if (device_kind != kFlutterPointerDeviceKindMouse) { - break; - } - - if (message == WM_LBUTTONDOWN) { - // Capture the pointer in case the user drags outside the client area. - // In this case, the "mouse leave" event is delayed until the user - // releases the button. It's only activated on left click given that - // it's more common for apps to handle dragging with only the left - // button. - SetCapture(window_handle_); - } - button_pressed = message; - if (message == WM_XBUTTONDOWN) { - button_pressed = GET_XBUTTON_WPARAM(wparam); - } - xPos = GET_X_LPARAM(lparam); - yPos = GET_Y_LPARAM(lparam); - OnPointerDown(static_cast(xPos), static_cast(yPos), - device_kind, kDefaultPointerDeviceId, button_pressed); - break; - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - case WM_XBUTTONUP: - device_kind = GetFlutterPointerDeviceKind(); - if (device_kind != kFlutterPointerDeviceKindMouse) { - break; - } - - if (message == WM_LBUTTONUP) { - ReleaseCapture(); - } - button_pressed = message; - if (message == WM_XBUTTONUP) { - button_pressed = GET_XBUTTON_WPARAM(wparam); - } - xPos = GET_X_LPARAM(lparam); - yPos = GET_Y_LPARAM(lparam); - OnPointerUp(static_cast(xPos), static_cast(yPos), - device_kind, kDefaultPointerDeviceId, button_pressed); - break; - case WM_MOUSEWHEEL: - OnScroll(0.0, - -(static_cast(HIWORD(wparam)) / - static_cast(WHEEL_DELTA)), - kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); - break; - case WM_MOUSEHWHEEL: - OnScroll((static_cast(HIWORD(wparam)) / - static_cast(WHEEL_DELTA)), - 0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId); - break; - case WM_GETOBJECT: { - LRESULT lresult = OnGetObject(message, wparam, lparam); - if (lresult) { - return lresult; - } - break; - } - case WM_TIMER: - if (wparam == kDirectManipulationTimer) { - direct_manipulation_owner_->Update(); - return 0; - } - break; - case DM_POINTERHITTEST: { - if (direct_manipulation_owner_) { - UINT contact_id = GET_POINTERID_WPARAM(wparam); - POINTER_INPUT_TYPE pointer_type; - if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) && - pointer_type == PT_TOUCHPAD) { - direct_manipulation_owner_->SetContact(contact_id); - } - } - break; - } - case WM_INPUTLANGCHANGE: - // TODO(cbracken): pass this to TextInputManager to aid with - // language-specific issues. - break; - case WM_IME_SETCONTEXT: - OnImeSetContext(message, wparam, lparam); - // Strip the ISC_SHOWUICOMPOSITIONWINDOW bit from lparam before passing it - // to DefWindowProc() so that the composition window is hidden since - // Flutter renders the composing string itself. - result_lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; - break; - case WM_IME_STARTCOMPOSITION: - OnImeStartComposition(message, wparam, lparam); - // Suppress further processing by DefWindowProc() so that the default - // system IME style isn't used, but rather the one set in the - // WM_IME_SETCONTEXT handler. - return TRUE; - case WM_IME_COMPOSITION: - OnImeComposition(message, wparam, lparam); - if (lparam & GCS_RESULTSTR || lparam & GCS_COMPSTR) { - // Suppress further processing by DefWindowProc() since otherwise it - // will emit the result string as WM_CHAR messages on commit. Instead, - // committing the composing text to the EditableText string is handled - // in TextInputModel::CommitComposing, triggered by - // OnImeEndComposition(). - return TRUE; - } - break; - case WM_IME_ENDCOMPOSITION: - OnImeEndComposition(message, wparam, lparam); - return TRUE; - case WM_IME_REQUEST: - OnImeRequest(message, wparam, lparam); - break; - case WM_UNICHAR: { - // Tell third-pary app, we can support Unicode. - if (wparam == UNICODE_NOCHAR) - return TRUE; - // DefWindowProc will send WM_CHAR for this WM_UNICHAR. - break; - } - case WM_THEMECHANGED: - OnThemeChange(); - break; - case WM_DEADCHAR: - case WM_SYSDEADCHAR: - case WM_CHAR: - case WM_SYSCHAR: - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - case WM_KEYUP: - case WM_SYSKEYUP: - if (keyboard_manager_->HandleMessage(message, wparam, lparam)) { - return 0; - } - break; - } - - return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); -} - -UINT Window::GetCurrentDPI() { - return current_dpi_; -} - -UINT Window::GetCurrentWidth() { - return current_width_; -} - -UINT Window::GetCurrentHeight() { - return current_height_; -} - -HWND Window::GetWindowHandle() { - return window_handle_; -} - -float Window::GetScrollOffsetMultiplier() { - return scroll_offset_multiplier_; -} - -void Window::UpdateScrollOffsetMultiplier() { - UINT lines_per_scroll = kLinesPerScrollWindowsDefault; - - // Get lines per scroll wheel value from Windows - SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_scroll, 0); - - // This logic is based off Chromium's implementation - // https://source.chromium.org/chromium/chromium/src/+/main:ui/events/blink/web_input_event_builders_win.cc;l=319-331 - scroll_offset_multiplier_ = - static_cast(lines_per_scroll) * 100.0 / 3.0; -} - -void Window::Destroy() { - if (window_handle_) { - text_input_manager_->SetWindowHandle(nullptr); - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - - UnregisterClass(window_class_name_.c_str(), nullptr); -} - -void Window::HandleResize(UINT width, UINT height) { - current_width_ = width; - current_height_ = height; - if (direct_manipulation_owner_) { - direct_manipulation_owner_->ResizeViewport(width, height); - } - OnResize(width, height); -} - -Window* Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); -} - -LRESULT Window::Win32DefWindowProc(HWND hWnd, - UINT Msg, - WPARAM wParam, - LPARAM lParam) { - return ::DefWindowProc(hWnd, Msg, wParam, lParam); -} - -BOOL Window::Win32PeekMessage(LPMSG lpMsg, - UINT wMsgFilterMin, - UINT wMsgFilterMax, - UINT wRemoveMsg) { - return ::PeekMessage(lpMsg, window_handle_, wMsgFilterMin, wMsgFilterMax, - wRemoveMsg); -} - -uint32_t Window::Win32MapVkToChar(uint32_t virtual_key) { - return ::MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR); -} - -UINT Window::Win32DispatchMessage(UINT Msg, WPARAM wParam, LPARAM lParam) { - return ::SendMessage(window_handle_, Msg, wParam, lParam); -} - -bool Window::GetHighContrastEnabled() { - HIGHCONTRAST high_contrast = {.cbSize = sizeof(HIGHCONTRAST)}; - // API call is only supported on Windows 8+ - if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), - &high_contrast, 0)) { - return high_contrast.dwFlags & HCF_HIGHCONTRASTON; - } else { - FML_LOG(INFO) << "Failed to get status of high contrast feature," - << "support only for Windows 8 + "; - return false; - } -} - -void Window::CreateAxFragmentRoot() { - if (ax_fragment_root_) { - return; - } - ax_fragment_root_ = std::make_unique( - window_handle_, GetAxFragmentRootDelegate()); - alert_delegate_ = - std::make_unique(*ax_fragment_root_); - ui::AXPlatformNode* alert_node = - ui::AXPlatformNodeWin::Create(alert_delegate_.get()); - alert_node_.reset(static_cast(alert_node)); - ax_fragment_root_->SetAlertNode(alert_node_.get()); -} - -} // namespace flutter diff --git a/shell/platform/windows/window.h b/shell/platform/windows/window.h deleted file mode 100644 index 015619c3d95cb..0000000000000 --- a/shell/platform/windows/window.h +++ /dev/null @@ -1,328 +0,0 @@ -// 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_FLUTTER_WIN32_WINDOW_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include -#include - -#include "flutter/shell/platform/common/alert_platform_node_delegate.h" -#include "flutter/shell/platform/embedder/embedder.h" -#include "flutter/shell/platform/windows/direct_manipulation.h" -#include "flutter/shell/platform/windows/keyboard_manager.h" -#include "flutter/shell/platform/windows/sequential_id_generator.h" -#include "flutter/shell/platform/windows/text_input_manager.h" -#include "flutter/shell/platform/windows/windows_lifecycle_manager.h" -#include "flutter/shell/platform/windows/windows_proc_table.h" -#include "flutter/shell/platform/windows/windowsx_shim.h" -#include "flutter/third_party/accessibility/ax/platform/ax_fragment_root_delegate_win.h" -#include "flutter/third_party/accessibility/ax/platform/ax_fragment_root_win.h" -#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h" -#include "flutter/third_party/accessibility/gfx/native_widget_types.h" - -namespace flutter { - -// A class abstraction for a high DPI aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling. -class Window : public KeyboardManager::WindowDelegate { - public: - Window(); - Window(std::unique_ptr windows_proc_table, - std::unique_ptr text_input_manager); - virtual ~Window(); - - // Initializes as a child window with size using |width| and |height| and - // |title| to identify the windowclass. Does not show window, window must be - // parented into window hierarchy by caller. - void InitializeChild(const char* title, - unsigned int width, - unsigned int height); - - HWND GetWindowHandle(); - - // |KeyboardManager::WindowDelegate| - virtual BOOL Win32PeekMessage(LPMSG lpMsg, - UINT wMsgFilterMin, - UINT wMsgFilterMax, - UINT wRemoveMsg) override; - - // |KeyboardManager::WindowDelegate| - virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) override; - - // |KeyboardManager::WindowDelegate| - virtual UINT Win32DispatchMessage(UINT Msg, - WPARAM wParam, - LPARAM lParam) override; - - protected: - // Converts a c string to a wide unicode string. - std::wstring NarrowToWide(const char* source); - - // Registers a window class with default style attributes, cursor and - // icon. - WNDCLASS RegisterWindowClass(std::wstring& title); - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - LRESULT HandleMessage(UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // When WM_DPICHANGE process it using |hWnd|, |wParam|. If - // |top_level| is set, extract the suggested new size from |lParam| and resize - // the window to the new suggested size. If |top_level| is not set, the - // |lParam| will not contain a suggested size hence ignore it. - LRESULT HandleDpiChange(HWND hWnd, - WPARAM wParam, - LPARAM lParam, - bool top_level); - - // Called when the DPI changes either when a - // user drags the window between monitors of differing DPI or when the user - // manually changes the scale factor. - virtual void OnDpiScale(UINT dpi) = 0; - - // Called when a resize occurs. - virtual void OnResize(UINT width, UINT height) = 0; - - // Called when a paint is requested. - virtual void OnPaint() = 0; - - // Called when the pointer moves within the - // window bounds. - virtual void OnPointerMove(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id, - int modifiers_state) = 0; - - // Called when the a mouse button, determined by |button|, goes down. - virtual void OnPointerDown(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id, - UINT button) = 0; - - // Called when the a mouse button, determined by |button|, goes from - // down to up - virtual void OnPointerUp(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id, - UINT button) = 0; - - // Called when the mouse leaves the window. - virtual void OnPointerLeave(double x, - double y, - FlutterPointerDeviceKind device_kind, - int32_t device_id) = 0; - - // Called when the cursor should be set for the client area. - virtual void OnSetCursor() = 0; - - // Called when the OS requests a COM object. - // - // The primary use of this function is to supply Windows with wrapped - // semantics objects for use by Windows accessibility. - virtual LRESULT OnGetObject(UINT const message, - WPARAM const wparam, - LPARAM const lparam); - - // Called when IME composing begins. - virtual void OnComposeBegin() = 0; - - // Called when IME composing text is committed. - virtual void OnComposeCommit() = 0; - - // Called when IME composing ends. - virtual void OnComposeEnd() = 0; - - // Called when IME composing text or cursor position changes. - virtual void OnComposeChange(const std::u16string& text, int cursor_pos) = 0; - - // Called when a window is activated in order to configure IME support for - // multi-step text input. - virtual void OnImeSetContext(UINT const message, - WPARAM const wparam, - LPARAM const lparam); - - // Called when multi-step text input begins when using an IME. - virtual void OnImeStartComposition(UINT const message, - WPARAM const wparam, - LPARAM const lparam); - - // Called when edits/commit of multi-step text input occurs when using an IME. - virtual void OnImeComposition(UINT const message, - WPARAM const wparam, - LPARAM const lparam); - - // Called when multi-step text input ends when using an IME. - virtual void OnImeEndComposition(UINT const message, - WPARAM const wparam, - LPARAM const lparam); - - // Called when the user triggers an IME-specific request such as input - // reconversion, where an existing input sequence is returned to composing - // mode to select an alternative candidate conversion. - virtual void OnImeRequest(UINT const message, - WPARAM const wparam, - LPARAM const lparam); - - // Called when the app ends IME composing, such as when the text input client - // is cleared or changed. - virtual void AbortImeComposing(); - - // Called when the cursor rect has been updated. - // - // |rect| is in Win32 window coordinates. - virtual void UpdateCursorRect(const Rect& rect); - - // Called when accessibility support is enabled or disabled. - virtual void OnUpdateSemanticsEnabled(bool enabled) = 0; - - // Called when mouse scrollwheel input occurs. - virtual void OnScroll(double delta_x, - double delta_y, - FlutterPointerDeviceKind device_kind, - int32_t device_id) = 0; - - UINT GetCurrentDPI(); - - UINT GetCurrentWidth(); - - UINT GetCurrentHeight(); - - // Returns the current pixel per scroll tick value. - virtual float GetScrollOffsetMultiplier(); - - // Check if the high contrast feature is enabled on the OS - virtual bool GetHighContrastEnabled(); - - // Creates the ax_fragment_root_, alert_delegate_ and alert_node_ if they do - // not yet exist. - // Once set, they are not reset to nullptr. - void CreateAxFragmentRoot(); - - // Called to obtain a pointer to the fragment root delegate. - virtual ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() = 0; - - // Called on a resize or focus event. - virtual void OnWindowStateEvent(WindowStateEvent event) = 0; - - protected: - // Win32's DefWindowProc. - // - // Used as the fallback behavior of HandleMessage. Exposed for dependency - // injection. - virtual LRESULT Win32DefWindowProc(HWND hWnd, - UINT Msg, - WPARAM wParam, - LPARAM lParam); - - // Returns the root view accessibility node, or nullptr if none. - virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0; - - // Release OS resources associated with the window. - void Destroy(); - - // Handles running DirectManipulation on the window to receive trackpad - // gestures. - std::unique_ptr direct_manipulation_owner_; - - // Called when a theme change message is issued - virtual void OnThemeChange() = 0; - - // Delegate to a alert_node_ used to set the announcement text. - std::unique_ptr alert_delegate_; - - // Accessibility node that represents an alert. - std::unique_ptr alert_node_; - - private: - // Activates tracking for a "mouse leave" event. - void TrackMouseLeaveEvent(HWND hwnd); - - // Stores new width and height and calls |OnResize| to notify inheritors - void HandleResize(UINT width, UINT height); - - // Retrieves a class instance pointer for |window| - static Window* GetThisFromHandle(HWND const window) noexcept; - - // Updates the cached scroll_offset_multiplier_ value based off OS settings. - void UpdateScrollOffsetMultiplier(); - - int current_dpi_ = 0; - int current_width_ = 0; - int current_height_ = 0; - - // Holds the conversion factor from lines scrolled to pixels scrolled. - float scroll_offset_multiplier_; - - // WM_DPICHANGED_BEFOREPARENT defined in more recent Windows - // SDK - const static long kWmDpiChangedBeforeParent = 0x02E2; - - // Member variable to hold window handle. - HWND window_handle_ = nullptr; - - // Member variable to hold the window title. - std::wstring window_class_name_; - - // Set to true to be notified when the mouse leaves the window. - bool tracking_mouse_leave_ = false; - - // Keeps track of the last key code produced by a WM_KEYDOWN or WM_SYSKEYDOWN - // message. - int keycode_for_char_message_ = 0; - - // Keeps track of the last mouse coordinates by a WM_MOUSEMOVE message. - double mouse_x_ = 0; - double mouse_y_ = 0; - - // Abstracts Windows APIs that may not be available on all supported versions - // of Windows. - std::unique_ptr windows_proc_table_; - - // Manages IME state. - std::unique_ptr text_input_manager_; - - // Manages IME state. - std::unique_ptr keyboard_manager_; - - // Used for temporarily storing the WM_TOUCH-provided touch points. - std::vector touch_points_; - - // Generates touch point IDs for touch events. - SequentialIdGenerator touch_id_generator_; - - // Timer identifier for DirectManipulation gesture polling. - const static int kDirectManipulationTimer = 1; - - // Implements IRawElementProviderFragmentRoot when UIA is enabled. - std::unique_ptr ax_fragment_root_; - - // Allow WindowAXFragmentRootDelegate to access protected method. - friend class WindowAXFragmentRootDelegate; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_WIN32_WINDOW_H_