mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Originally font change notification was handled by forwarding WM_FONTCHANGE to the Flutter HWND, to avoid adding new API surface, but that's not a good solution in a multi-window scenario, and it would require a completely different solution for UWP. It also requires non-obvious plumbing in the runner. This replaces that with an explicit API, so that there's a clean and obvious way for the runner to trigger this event.
315 lines
10 KiB
C++
315 lines
10 KiB
C++
// 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 <cstring>
|
|
|
|
#include "win32_dpi_utils.h"
|
|
|
|
namespace flutter {
|
|
|
|
namespace {
|
|
char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) {
|
|
return 0x10000 + ((static_cast<char32_t>(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<LPWSTR>(&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<CREATESTRUCT*>(lparam);
|
|
SetWindowLongPtr(window, GWLP_USERDATA,
|
|
reinterpret_cast<LONG_PTR>(cs->lpCreateParams));
|
|
|
|
auto that = static_cast<Win32Window*>(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<double>(xPos), static_cast<double>(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<double>(xPos), static_cast<double>(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<double>(xPos), static_cast<double>(yPos),
|
|
button_pressed);
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
OnScroll(0.0, -(static_cast<short>(HIWORD(wparam)) /
|
|
static_cast<double>(WHEEL_DELTA)));
|
|
break;
|
|
case WM_MOUSEHWHEEL:
|
|
OnScroll((static_cast<short>(HIWORD(wparam)) /
|
|
static_cast<double>(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<wchar_t>(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<Win32Window*>(
|
|
GetWindowLongPtr(window, GWLP_USERDATA));
|
|
}
|
|
|
|
} // namespace flutter
|