// 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/win32_window.h" #include #include "win32_dpi_utils.h" namespace flutter { namespace { char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) { return 0x10000 + ((static_cast(high) & 0x000003FF) << 10) + (low & 0x3FF); } } // namespace Win32Window::Win32Window() { // 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); } Win32Window::~Win32Window() { Destroy(); } void Win32Window::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); } } std::wstring Win32Window::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 Win32Window::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 Win32Window::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; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->HandleMessage(message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } void Win32Window::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 Win32Window::HandleMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { int xPos = 0, yPos = 0; UINT width = 0, height = 0; UINT button_pressed = 0; 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); break; case WM_MOUSEMOVE: TrackMouseLeaveEvent(window_handle_); xPos = GET_X_LPARAM(lparam); yPos = GET_Y_LPARAM(lparam); OnPointerMove(static_cast(xPos), static_cast(yPos)); break; case WM_MOUSELEAVE:; OnPointerLeave(); // 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_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: 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), button_pressed); break; case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: 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), button_pressed); break; case WM_MOUSEWHEEL: OnScroll(0.0, -(static_cast(HIWORD(wparam)) / static_cast(WHEEL_DELTA))); break; case WM_MOUSEHWHEEL: OnScroll((static_cast(HIWORD(wparam)) / static_cast(WHEEL_DELTA)), 0.0); 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_DEADCHAR: case WM_SYSDEADCHAR: case WM_CHAR: case WM_SYSCHAR: { static wchar_t s_pending_high_surrogate = 0; wchar_t character = static_cast(wparam); std::u16string text({character}); char32_t code_point = character; if (IS_HIGH_SURROGATE(character)) { // Save to send later with the trailing surrogate. s_pending_high_surrogate = character; } else if (IS_LOW_SURROGATE(character) && s_pending_high_surrogate != 0) { text.insert(text.begin(), s_pending_high_surrogate); // Merge the surrogate pairs for the key event. code_point = CodePointFromSurrogatePair(s_pending_high_surrogate, character); s_pending_high_surrogate = 0; } // Of the messages handled here, only WM_CHAR should be treated as // characters. WM_SYS*CHAR are not part of text input, and WM_DEADCHAR // will be incorporated into a later WM_CHAR with the full character. // Also filter out: // - Lead surrogates, which like dead keys will be send once combined. // - ASCII control characters, which are sent as WM_CHAR events for all // control key shortcuts. if (message == WM_CHAR && s_pending_high_surrogate == 0 && character >= u' ') { OnText(text); } // All key presses that generate a character should be sent from // WM_CHAR. In order to send the full key press information, the keycode // is persisted in keycode_for_char_message_ obtained from WM_KEYDOWN. if (keycode_for_char_message_ != 0) { const unsigned int scancode = (lparam >> 16) & 0xff; OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, code_point); keycode_for_char_message_ = 0; } break; } case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: const bool is_keydown_message = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); // Check if this key produces a character. If so, the key press should // be sent with the character produced at WM_CHAR. Store the produced // keycode (it's not accessible from WM_CHAR) to be used in WM_CHAR. const unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); if (character > 0 && is_keydown_message) { keycode_for_char_message_ = wparam; break; } unsigned int keyCode(wparam); const unsigned int scancode = (lparam >> 16) & 0xff; // If the key is a modifier, get its side. if (keyCode == VK_SHIFT || keyCode == VK_MENU || keyCode == VK_CONTROL) { keyCode = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX); } const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; OnKey(keyCode, scancode, action, 0); break; } return DefWindowProc(window_handle_, message, wparam, lparam); } UINT Win32Window::GetCurrentDPI() { return current_dpi_; } UINT Win32Window::GetCurrentWidth() { return current_width_; } UINT Win32Window::GetCurrentHeight() { return current_height_; } HWND Win32Window::GetWindowHandle() { return window_handle_; } void Win32Window::Destroy() { if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } UnregisterClass(window_class_name_.c_str(), nullptr); } void Win32Window::HandleResize(UINT width, UINT height) { current_width_ = width; current_height_ = height; OnResize(width, height); } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } } // namespace flutter