Flutter Engine
win32_window.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "flutter/shell/platform/windows/win32_window.h"
6 
7 #include <cstring>
8 
9 #include "win32_dpi_utils.h"
10 
11 namespace flutter {
12 
13 namespace {
14 char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) {
15  return 0x10000 + ((static_cast<char32_t>(high) & 0x000003FF) << 10) +
16  (low & 0x3FF);
17 }
18 } // namespace
19 
21  // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is
22  // supported, |current_dpi_| should be updated in the
23  // kWmDpiChangedBeforeParent message.
24  current_dpi_ = GetDpiForHWND(nullptr);
25 }
26 
28  Destroy();
29 }
30 
31 void Win32Window::InitializeChild(const char* title,
32  unsigned int width,
33  unsigned int height) {
34  Destroy();
35  std::wstring converted_title = NarrowToWide(title);
36 
37  WNDCLASS window_class = RegisterWindowClass(converted_title);
38 
39  auto* result = CreateWindowEx(
40  0, window_class.lpszClassName, converted_title.c_str(),
41  WS_CHILD | WS_VISIBLE, CW_DEFAULT, CW_DEFAULT, width, height,
42  HWND_MESSAGE, nullptr, window_class.hInstance, this);
43 
44  if (result == nullptr) {
45  auto error = GetLastError();
46  LPWSTR message = nullptr;
47  size_t size = FormatMessageW(
48  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
49  FORMAT_MESSAGE_IGNORE_INSERTS,
50  NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
51  reinterpret_cast<LPWSTR>(&message), 0, NULL);
52  OutputDebugString(message);
53  LocalFree(message);
54  }
55 }
56 
57 std::wstring Win32Window::NarrowToWide(const char* source) {
58  size_t length = strlen(source);
59  size_t outlen = 0;
60  std::wstring wideTitle(length, L'#');
61  mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length);
62  return wideTitle;
63 }
64 
65 WNDCLASS Win32Window::RegisterWindowClass(std::wstring& title) {
66  window_class_name_ = title;
67 
68  WNDCLASS window_class{};
69  window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
70  window_class.lpszClassName = title.c_str();
71  window_class.style = CS_HREDRAW | CS_VREDRAW;
72  window_class.cbClsExtra = 0;
73  window_class.cbWndExtra = 0;
74  window_class.hInstance = GetModuleHandle(nullptr);
75  window_class.hIcon = nullptr;
76  window_class.hbrBackground = 0;
77  window_class.lpszMenuName = nullptr;
78  window_class.lpfnWndProc = WndProc;
79  RegisterClass(&window_class);
80  return window_class;
81 }
82 
83 LRESULT CALLBACK Win32Window::WndProc(HWND const window,
84  UINT const message,
85  WPARAM const wparam,
86  LPARAM const lparam) noexcept {
87  if (message == WM_NCCREATE) {
88  auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
89  SetWindowLongPtr(window, GWLP_USERDATA,
90  reinterpret_cast<LONG_PTR>(cs->lpCreateParams));
91 
92  auto that = static_cast<Win32Window*>(cs->lpCreateParams);
93  that->window_handle_ = window;
94  } else if (Win32Window* that = GetThisFromHandle(window)) {
95  return that->HandleMessage(message, wparam, lparam);
96  }
97 
98  return DefWindowProc(window, message, wparam, lparam);
99 }
100 
101 void Win32Window::TrackMouseLeaveEvent(HWND hwnd) {
102  if (!tracking_mouse_leave_) {
103  TRACKMOUSEEVENT tme;
104  tme.cbSize = sizeof(tme);
105  tme.hwndTrack = hwnd;
106  tme.dwFlags = TME_LEAVE;
107  TrackMouseEvent(&tme);
108  tracking_mouse_leave_ = true;
109  }
110 }
111 
112 LRESULT
113 Win32Window::HandleMessage(UINT const message,
114  WPARAM const wparam,
115  LPARAM const lparam) noexcept {
116  int xPos = 0, yPos = 0;
117  UINT width = 0, height = 0;
118  UINT button_pressed = 0;
119 
120  switch (message) {
121  case kWmDpiChangedBeforeParent:
122  current_dpi_ = GetDpiForHWND(window_handle_);
123  OnDpiScale(current_dpi_);
124  return 0;
125  case WM_SIZE:
126  width = LOWORD(lparam);
127  height = HIWORD(lparam);
128 
129  current_width_ = width;
130  current_height_ = height;
131  HandleResize(width, height);
132  break;
133  case WM_MOUSEMOVE:
134  TrackMouseLeaveEvent(window_handle_);
135 
136  xPos = GET_X_LPARAM(lparam);
137  yPos = GET_Y_LPARAM(lparam);
138  OnPointerMove(static_cast<double>(xPos), static_cast<double>(yPos));
139  break;
140  case WM_MOUSELEAVE:;
141  OnPointerLeave();
142  // Once the tracked event is received, the TrackMouseEvent function
143  // resets. Set to false to make sure it's called once mouse movement is
144  // detected again.
145  tracking_mouse_leave_ = false;
146  break;
147  case WM_SETCURSOR: {
148  UINT hit_test_result = LOWORD(lparam);
149  if (hit_test_result == HTCLIENT) {
150  OnSetCursor();
151  return TRUE;
152  }
153  break;
154  }
155  case WM_LBUTTONDOWN:
156  case WM_RBUTTONDOWN:
157  case WM_MBUTTONDOWN:
158  case WM_XBUTTONDOWN:
159  if (message == WM_LBUTTONDOWN) {
160  // Capture the pointer in case the user drags outside the client area.
161  // In this case, the "mouse leave" event is delayed until the user
162  // releases the button. It's only activated on left click given that
163  // it's more common for apps to handle dragging with only the left
164  // button.
165  SetCapture(window_handle_);
166  }
167  button_pressed = message;
168  if (message == WM_XBUTTONDOWN) {
169  button_pressed = GET_XBUTTON_WPARAM(wparam);
170  }
171  xPos = GET_X_LPARAM(lparam);
172  yPos = GET_Y_LPARAM(lparam);
173  OnPointerDown(static_cast<double>(xPos), static_cast<double>(yPos),
174  button_pressed);
175  break;
176  case WM_LBUTTONUP:
177  case WM_RBUTTONUP:
178  case WM_MBUTTONUP:
179  case WM_XBUTTONUP:
180  if (message == WM_LBUTTONUP) {
181  ReleaseCapture();
182  }
183  button_pressed = message;
184  if (message == WM_XBUTTONUP) {
185  button_pressed = GET_XBUTTON_WPARAM(wparam);
186  }
187  xPos = GET_X_LPARAM(lparam);
188  yPos = GET_Y_LPARAM(lparam);
189  OnPointerUp(static_cast<double>(xPos), static_cast<double>(yPos),
190  button_pressed);
191  break;
192  case WM_MOUSEWHEEL:
193  OnScroll(0.0, -(static_cast<short>(HIWORD(wparam)) /
194  static_cast<double>(WHEEL_DELTA)));
195  break;
196  case WM_MOUSEHWHEEL:
197  OnScroll((static_cast<short>(HIWORD(wparam)) /
198  static_cast<double>(WHEEL_DELTA)),
199  0.0);
200  break;
201  case WM_UNICHAR: {
202  // Tell third-pary app, we can support Unicode.
203  if (wparam == UNICODE_NOCHAR)
204  return TRUE;
205  // DefWindowProc will send WM_CHAR for this WM_UNICHAR.
206  break;
207  }
208  case WM_DEADCHAR:
209  case WM_SYSDEADCHAR:
210  case WM_CHAR:
211  case WM_SYSCHAR: {
212  static wchar_t s_pending_high_surrogate = 0;
213 
214  wchar_t character = static_cast<wchar_t>(wparam);
215  std::u16string text({character});
216  char32_t code_point = character;
217  if (IS_HIGH_SURROGATE(character)) {
218  // Save to send later with the trailing surrogate.
219  s_pending_high_surrogate = character;
220  } else if (IS_LOW_SURROGATE(character) && s_pending_high_surrogate != 0) {
221  text.insert(text.begin(), s_pending_high_surrogate);
222  // Merge the surrogate pairs for the key event.
223  code_point =
224  CodePointFromSurrogatePair(s_pending_high_surrogate, character);
225  s_pending_high_surrogate = 0;
226  }
227 
228  // Of the messages handled here, only WM_CHAR should be treated as
229  // characters. WM_SYS*CHAR are not part of text input, and WM_DEADCHAR
230  // will be incorporated into a later WM_CHAR with the full character.
231  // Also filter out:
232  // - Lead surrogates, which like dead keys will be send once combined.
233  // - ASCII control characters, which are sent as WM_CHAR events for all
234  // control key shortcuts.
235  if (message == WM_CHAR && s_pending_high_surrogate == 0 &&
236  character >= u' ') {
237  OnText(text);
238  }
239 
240  // All key presses that generate a character should be sent from
241  // WM_CHAR. In order to send the full key press information, the keycode
242  // is persisted in keycode_for_char_message_ obtained from WM_KEYDOWN.
243  if (keycode_for_char_message_ != 0) {
244  const unsigned int scancode = (lparam >> 16) & 0xff;
245  OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, code_point);
246  keycode_for_char_message_ = 0;
247  }
248  break;
249  }
250  case WM_KEYDOWN:
251  case WM_SYSKEYDOWN:
252  case WM_KEYUP:
253  case WM_SYSKEYUP:
254  const bool is_keydown_message =
255  (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
256  // Check if this key produces a character. If so, the key press should
257  // be sent with the character produced at WM_CHAR. Store the produced
258  // keycode (it's not accessible from WM_CHAR) to be used in WM_CHAR.
259  const unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR);
260  if (character > 0 && is_keydown_message) {
261  keycode_for_char_message_ = wparam;
262  break;
263  }
264  unsigned int keyCode(wparam);
265  const unsigned int scancode = (lparam >> 16) & 0xff;
266  // If the key is a modifier, get its side.
267  if (keyCode == VK_SHIFT || keyCode == VK_MENU || keyCode == VK_CONTROL) {
268  keyCode = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);
269  }
270  const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP;
271  OnKey(keyCode, scancode, action, 0);
272  break;
273  }
274 
275  return DefWindowProc(window_handle_, message, wparam, lparam);
276 }
277 
279  return current_dpi_;
280 }
281 
283  return current_width_;
284 }
285 
287  return current_height_;
288 }
289 
291  return window_handle_;
292 }
293 
294 void Win32Window::Destroy() {
295  if (window_handle_) {
296  DestroyWindow(window_handle_);
297  window_handle_ = nullptr;
298  }
299 
300  UnregisterClass(window_class_name_.c_str(), nullptr);
301 }
302 
303 void Win32Window::HandleResize(UINT width, UINT height) {
304  current_width_ = width;
305  current_height_ = height;
306  OnResize(width, height);
307 }
308 
309 Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
310  return reinterpret_cast<Win32Window*>(
311  GetWindowLongPtr(window, GWLP_USERDATA));
312 }
313 
314 } // namespace flutter
WNDCLASS RegisterWindowClass(std::wstring &title)
Definition: win32_window.cc:65
virtual void OnDpiScale(UINT dpi)=0
virtual void OnSetCursor()=0
UINT GetDpiForHWND(HWND hwnd)
FlMethodResponse GError ** error
virtual void OnText(const std::u16string &text)=0
void InitializeChild(const char *title, unsigned int width, unsigned int height)
Definition: win32_window.cc:31
constexpr std::size_t size(T(&array)[N])
Definition: size.h:13
virtual void OnKey(int key, int scancode, int action, char32_t character)=0
virtual void OnPointerUp(double x, double y, UINT button)=0
static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
Definition: win32_window.cc:83
virtual void OnScroll(double delta_x, double delta_y)=0
SemanticsAction action
size_t length
int32_t height
int32_t width
virtual void OnPointerLeave()=0
std::wstring NarrowToWide(const char *source)
Definition: win32_window.cc:57
virtual void OnPointerDown(double x, double y, UINT button)=0
return TRUE
Definition: fl_view.cc:107
LRESULT HandleMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
virtual void OnResize(UINT width, UINT height)=0
virtual void OnPointerMove(double x, double y)=0