Flutter Windows Embedder
flutter_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 
6 
7 #include <WinUser.h>
8 #include <dwmapi.h>
9 
10 #include <chrono>
11 #include <map>
12 
13 #include "flutter/fml/logging.h"
14 #include "flutter/shell/platform/embedder/embedder.h"
19 
20 namespace flutter {
21 
22 namespace {
23 
24 // The Windows DPI system is based on this
25 // constant for machines running at 100% scaling.
26 constexpr int base_dpi = 96;
27 
28 static const int kMinTouchDeviceId = 0;
29 static const int kMaxTouchDeviceId = 128;
30 
31 static const int kLinesPerScrollWindowsDefault = 3;
32 
33 // Maps a Flutter cursor name to an HCURSOR.
34 //
35 // Returns the arrow cursor for unknown constants.
36 //
37 // This map must be kept in sync with Flutter framework's
38 // services/mouse_cursor.dart.
39 static HCURSOR GetCursorByName(const std::string& cursor_name) {
40  static auto* cursors = new std::map<std::string, const wchar_t*>{
41  {"allScroll", IDC_SIZEALL},
42  {"basic", IDC_ARROW},
43  {"click", IDC_HAND},
44  {"forbidden", IDC_NO},
45  {"help", IDC_HELP},
46  {"move", IDC_SIZEALL},
47  {"none", nullptr},
48  {"noDrop", IDC_NO},
49  {"precise", IDC_CROSS},
50  {"progress", IDC_APPSTARTING},
51  {"text", IDC_IBEAM},
52  {"resizeColumn", IDC_SIZEWE},
53  {"resizeDown", IDC_SIZENS},
54  {"resizeDownLeft", IDC_SIZENESW},
55  {"resizeDownRight", IDC_SIZENWSE},
56  {"resizeLeft", IDC_SIZEWE},
57  {"resizeLeftRight", IDC_SIZEWE},
58  {"resizeRight", IDC_SIZEWE},
59  {"resizeRow", IDC_SIZENS},
60  {"resizeUp", IDC_SIZENS},
61  {"resizeUpDown", IDC_SIZENS},
62  {"resizeUpLeft", IDC_SIZENWSE},
63  {"resizeUpRight", IDC_SIZENESW},
64  {"resizeUpLeftDownRight", IDC_SIZENWSE},
65  {"resizeUpRightDownLeft", IDC_SIZENESW},
66  {"wait", IDC_WAIT},
67  };
68  const wchar_t* idc_name = IDC_ARROW;
69  auto it = cursors->find(cursor_name);
70  if (it != cursors->end()) {
71  idc_name = it->second;
72  }
73  return ::LoadCursor(nullptr, idc_name);
74 }
75 
76 static constexpr int32_t kDefaultPointerDeviceId = 0;
77 
78 // This method is only valid during a window message related to mouse/touch
79 // input.
80 // See
81 // https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages?redirectedfrom=MSDN#distinguishing-pen-input-from-mouse-and-touch.
82 static FlutterPointerDeviceKind GetFlutterPointerDeviceKind() {
83  constexpr LPARAM kTouchOrPenSignature = 0xFF515700;
84  constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80;
85  constexpr LPARAM kSignatureMask = 0xFFFFFF00;
86  LPARAM info = GetMessageExtraInfo();
87  if ((info & kSignatureMask) == kTouchOrPenSignature) {
88  if ((info & kTouchSignature) == kTouchSignature) {
89  return kFlutterPointerDeviceKindTouch;
90  }
91  return kFlutterPointerDeviceKindStylus;
92  }
93  return kFlutterPointerDeviceKindMouse;
94 }
95 
96 // Translates button codes from Win32 API to FlutterPointerMouseButtons.
97 static uint64_t ConvertWinButtonToFlutterButton(UINT button) {
98  switch (button) {
99  case WM_LBUTTONDOWN:
100  case WM_LBUTTONUP:
101  return kFlutterPointerButtonMousePrimary;
102  case WM_RBUTTONDOWN:
103  case WM_RBUTTONUP:
104  return kFlutterPointerButtonMouseSecondary;
105  case WM_MBUTTONDOWN:
106  case WM_MBUTTONUP:
107  return kFlutterPointerButtonMouseMiddle;
108  case XBUTTON1:
109  return kFlutterPointerButtonMouseBack;
110  case XBUTTON2:
111  return kFlutterPointerButtonMouseForward;
112  }
113  FML_LOG(WARNING) << "Mouse button not recognized: " << button;
114  return 0;
115 }
116 
117 } // namespace
118 
120  int width,
121  int height,
122  std::shared_ptr<WindowsProcTable> windows_proc_table,
123  std::unique_ptr<TextInputManager> text_input_manager)
124  : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId),
125  windows_proc_table_(std::move(windows_proc_table)),
126  text_input_manager_(std::move(text_input_manager)),
127  ax_fragment_root_(nullptr) {
128  // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is
129  // supported, |current_dpi_| should be updated in the
130  // kWmDpiChangedBeforeParent message.
131  current_dpi_ = GetDpiForHWND(nullptr);
132 
133  // Get initial value for wheel scroll lines
134  // TODO: Listen to changes for this value
135  // https://github.com/flutter/flutter/issues/107248
136  UpdateScrollOffsetMultiplier();
137 
138  if (windows_proc_table_ == nullptr) {
139  windows_proc_table_ = std::make_unique<WindowsProcTable>();
140  }
141  if (text_input_manager_ == nullptr) {
142  text_input_manager_ = std::make_unique<TextInputManager>();
143  }
144  keyboard_manager_ = std::make_unique<KeyboardManager>(this);
145 
146  InitializeChild("FLUTTERVIEW", width, height);
147  current_cursor_ = ::LoadCursor(nullptr, IDC_ARROW);
148 }
149 
150 // Base constructor for mocks
152  : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId) {}
153 
155  Destroy();
156 }
157 
159  binding_handler_delegate_ = window;
161  direct_manipulation_owner_->SetBindingHandlerDelegate(window);
162  }
163  if (restored_ && window) {
165  }
166  if (focused_ && window) {
168  }
169 }
170 
172  return static_cast<float>(GetCurrentDPI()) / static_cast<float>(base_dpi);
173 }
174 
176  return {GetCurrentWidth(), GetCurrentHeight()};
177 }
178 
179 void FlutterWindow::UpdateFlutterCursor(const std::string& cursor_name) {
180  SetFlutterCursor(GetCursorByName(cursor_name));
181 }
182 
183 void FlutterWindow::SetFlutterCursor(HCURSOR cursor) {
184  current_cursor_ = cursor;
185  ::SetCursor(current_cursor_);
186 }
187 
188 void FlutterWindow::OnDpiScale(unsigned int dpi) {};
189 
190 // When DesktopWindow notifies that a WM_Size message has come in
191 // lets FlutterEngine know about the new size.
192 void FlutterWindow::OnResize(unsigned int width, unsigned int height) {
193  if (binding_handler_delegate_ != nullptr) {
194  binding_handler_delegate_->OnWindowSizeChanged(width, height);
195  }
196 }
197 
199  if (binding_handler_delegate_ != nullptr) {
200  binding_handler_delegate_->OnWindowRepaint();
201  }
202 }
203 
205  double y,
206  FlutterPointerDeviceKind device_kind,
207  int32_t device_id,
208  int modifiers_state) {
209  binding_handler_delegate_->OnPointerMove(x, y, device_kind, device_id,
210  modifiers_state);
211 }
212 
214  double y,
215  FlutterPointerDeviceKind device_kind,
216  int32_t device_id,
217  UINT button) {
218  uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);
219  if (flutter_button != 0) {
220  binding_handler_delegate_->OnPointerDown(
221  x, y, device_kind, device_id,
222  static_cast<FlutterPointerMouseButtons>(flutter_button));
223  }
224 }
225 
227  double y,
228  FlutterPointerDeviceKind device_kind,
229  int32_t device_id,
230  UINT button) {
231  uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);
232  if (flutter_button != 0) {
233  binding_handler_delegate_->OnPointerUp(
234  x, y, device_kind, device_id,
235  static_cast<FlutterPointerMouseButtons>(flutter_button));
236  }
237 }
238 
240  double y,
241  FlutterPointerDeviceKind device_kind,
242  int32_t device_id) {
243  binding_handler_delegate_->OnPointerLeave(x, y, device_kind, device_id);
244 }
245 
247  ::SetCursor(current_cursor_);
248 }
249 
250 void FlutterWindow::OnText(const std::u16string& text) {
251  binding_handler_delegate_->OnText(text);
252 }
253 
255  int scancode,
256  int action,
257  char32_t character,
258  bool extended,
259  bool was_down,
261  binding_handler_delegate_->OnKey(key, scancode, action, character, extended,
262  was_down, std::move(callback));
263 }
264 
266  binding_handler_delegate_->OnComposeBegin();
267 }
268 
270  binding_handler_delegate_->OnComposeCommit();
271 }
272 
274  binding_handler_delegate_->OnComposeEnd();
275 }
276 
277 void FlutterWindow::OnComposeChange(const std::u16string& text,
278  int cursor_pos) {
279  binding_handler_delegate_->OnComposeChange(text, cursor_pos);
280 }
281 
283  binding_handler_delegate_->OnUpdateSemanticsEnabled(enabled);
284 }
285 
286 void FlutterWindow::OnScroll(double delta_x,
287  double delta_y,
288  FlutterPointerDeviceKind device_kind,
289  int32_t device_id) {
290  POINT point;
291  GetCursorPos(&point);
292 
293  ScreenToClient(GetWindowHandle(), &point);
294  binding_handler_delegate_->OnScroll(point.x, point.y, delta_x, delta_y,
295  GetScrollOffsetMultiplier(), device_kind,
296  device_id);
297 }
298 
300  // Convert the rect from Flutter logical coordinates to device coordinates.
301  auto scale = GetDpiScale();
302  Point origin(rect.left() * scale, rect.top() * scale);
303  Size size(rect.width() * scale, rect.height() * scale);
304  UpdateCursorRect(Rect(origin, size));
305 }
306 
309 }
310 
312  HDC dc = ::GetDC(GetWindowHandle());
313  bool result = ::PatBlt(dc, 0, 0, current_width_, current_height_, BLACKNESS);
314  ::ReleaseDC(GetWindowHandle(), dc);
315  return result;
316 }
317 
318 bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation,
319  size_t row_bytes,
320  size_t height) {
321  HDC dc = ::GetDC(GetWindowHandle());
322  BITMAPINFO bmi = {};
323  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
324  bmi.bmiHeader.biWidth = row_bytes / 4;
325  bmi.bmiHeader.biHeight = -height;
326  bmi.bmiHeader.biPlanes = 1;
327  bmi.bmiHeader.biBitCount = 32;
328  bmi.bmiHeader.biCompression = BI_RGB;
329  bmi.bmiHeader.biSizeImage = 0;
330  int ret = ::SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0,
331  height, allocation, &bmi, DIB_RGB_COLORS);
332  ::ReleaseDC(GetWindowHandle(), dc);
333  return ret != 0;
334 }
335 
336 gfx::NativeViewAccessible FlutterWindow::GetNativeViewAccessible() {
337  if (binding_handler_delegate_ == nullptr) {
338  return nullptr;
339  }
340 
341  return binding_handler_delegate_->GetNativeViewAccessible();
342 }
343 
345  POINT point;
346  GetCursorPos(&point);
347  ScreenToClient(GetWindowHandle(), &point);
348  return {(size_t)point.x, (size_t)point.y};
349 }
350 
352  binding_handler_delegate_->OnHighContrastChanged();
353 }
354 
355 ui::AXFragmentRootDelegateWin* FlutterWindow::GetAxFragmentRootDelegate() {
356  return binding_handler_delegate_->GetAxFragmentRootDelegate();
357 }
358 
360  CreateAxFragmentRoot();
361  return alert_delegate_.get();
362 }
363 
364 ui::AXPlatformNodeWin* FlutterWindow::GetAlert() {
365  CreateAxFragmentRoot();
366  return alert_node_.get();
367 }
368 
370  switch (event) {
372  restored_ = true;
373  break;
375  restored_ = false;
376  focused_ = false;
377  break;
379  focused_ = true;
380  break;
382  focused_ = false;
383  break;
384  }
385  HWND hwnd = GetWindowHandle();
386  if (hwnd && binding_handler_delegate_) {
387  binding_handler_delegate_->OnWindowStateEvent(hwnd, event);
388  }
389 }
390 
391 void FlutterWindow::TrackMouseLeaveEvent(HWND hwnd) {
392  if (!tracking_mouse_leave_) {
393  TRACKMOUSEEVENT tme;
394  tme.cbSize = sizeof(tme);
395  tme.hwndTrack = hwnd;
396  tme.dwFlags = TME_LEAVE;
397  TrackMouseEvent(&tme);
398  tracking_mouse_leave_ = true;
399  }
400 }
401 
402 void FlutterWindow::HandleResize(UINT width, UINT height) {
403  current_width_ = width;
404  current_height_ = height;
406  direct_manipulation_owner_->ResizeViewport(width, height);
407  }
408  OnResize(width, height);
409 }
410 
411 FlutterWindow* FlutterWindow::GetThisFromHandle(HWND const window) noexcept {
412  return reinterpret_cast<FlutterWindow*>(
413  GetWindowLongPtr(window, GWLP_USERDATA));
414 }
415 
416 void FlutterWindow::UpdateScrollOffsetMultiplier() {
417  UINT lines_per_scroll = kLinesPerScrollWindowsDefault;
418 
419  // Get lines per scroll wheel value from Windows
420  SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_scroll, 0);
421 
422  // This logic is based off Chromium's implementation
423  // https://source.chromium.org/chromium/chromium/src/+/main:ui/events/blink/web_input_event_builders_win.cc;l=319-331
424  scroll_offset_multiplier_ =
425  static_cast<float>(lines_per_scroll) * 100.0 / 3.0;
426 }
427 
428 void FlutterWindow::InitializeChild(const char* title,
429  unsigned int width,
430  unsigned int height) {
431  Destroy();
432  std::wstring converted_title = NarrowToWide(title);
433 
434  WNDCLASS window_class = RegisterWindowClass(converted_title);
435 
436  auto* result = CreateWindowEx(
437  0, window_class.lpszClassName, converted_title.c_str(),
438  WS_CHILD | WS_VISIBLE, CW_DEFAULT, CW_DEFAULT, width, height,
439  HWND_MESSAGE, nullptr, window_class.hInstance, this);
440 
441  if (result == nullptr) {
442  auto error = GetLastError();
443  LPWSTR message = nullptr;
444  size_t size = FormatMessageW(
445  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
446  FORMAT_MESSAGE_IGNORE_INSERTS,
447  NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
448  reinterpret_cast<LPWSTR>(&message), 0, NULL);
449  OutputDebugString(message);
450  LocalFree(message);
451  }
452  SetUserObjectInformationA(GetCurrentProcess(),
453  UOI_TIMERPROC_EXCEPTION_SUPPRESSION, FALSE, 1);
454  // SetTimer is not precise, if a 16 ms interval is requested, it will instead
455  // often fire in an interval of 32 ms. Providing a value of 14 will ensure it
456  // runs every 16 ms, which will allow for 60 Hz trackpad gesture events, which
457  // is the maximal frequency supported by SetTimer.
458  SetTimer(result, kDirectManipulationTimer, 14, nullptr);
459  direct_manipulation_owner_ = std::make_unique<DirectManipulationOwner>(this);
460  direct_manipulation_owner_->Init(width, height);
461 }
462 
464  return window_handle_;
465 }
466 
468  UINT wMsgFilterMin,
469  UINT wMsgFilterMax,
470  UINT wRemoveMsg) {
471  return ::PeekMessage(lpMsg, window_handle_, wMsgFilterMin, wMsgFilterMax,
472  wRemoveMsg);
473 }
474 
475 uint32_t FlutterWindow::Win32MapVkToChar(uint32_t virtual_key) {
476  return ::MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR);
477 }
478 
480  WPARAM wParam,
481  LPARAM lParam) {
482  return ::SendMessage(window_handle_, Msg, wParam, lParam);
483 }
484 
485 std::wstring FlutterWindow::NarrowToWide(const char* source) {
486  size_t length = strlen(source);
487  size_t outlen = 0;
488  std::wstring wideTitle(length, L'#');
489  mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length);
490  return wideTitle;
491 }
492 
493 WNDCLASS FlutterWindow::RegisterWindowClass(std::wstring& title) {
494  window_class_name_ = title;
495 
496  WNDCLASS window_class{};
497  window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
498  window_class.lpszClassName = title.c_str();
499  window_class.style = CS_HREDRAW | CS_VREDRAW;
500  window_class.cbClsExtra = 0;
501  window_class.cbWndExtra = 0;
502  window_class.hInstance = GetModuleHandle(nullptr);
503  window_class.hIcon = nullptr;
504  window_class.hbrBackground = 0;
505  window_class.lpszMenuName = nullptr;
506  window_class.lpfnWndProc = WndProc;
507  RegisterClass(&window_class);
508  return window_class;
509 }
510 
511 LRESULT CALLBACK FlutterWindow::WndProc(HWND const window,
512  UINT const message,
513  WPARAM const wparam,
514  LPARAM const lparam) noexcept {
515  if (message == WM_NCCREATE) {
516  auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
517  SetWindowLongPtr(window, GWLP_USERDATA,
518  reinterpret_cast<LONG_PTR>(cs->lpCreateParams));
519 
520  auto that = static_cast<FlutterWindow*>(cs->lpCreateParams);
521  that->window_handle_ = window;
522  that->text_input_manager_->SetWindowHandle(window);
523  RegisterTouchWindow(window, 0);
524  } else if (FlutterWindow* that = GetThisFromHandle(window)) {
525  return that->HandleMessage(message, wparam, lparam);
526  }
527 
528  return DefWindowProc(window, message, wparam, lparam);
529 }
530 
531 LRESULT
533  WPARAM const wparam,
534  LPARAM const lparam) noexcept {
535  LPARAM result_lparam = lparam;
536  int xPos = 0, yPos = 0;
537  UINT width = 0, height = 0;
538  UINT button_pressed = 0;
539  FlutterPointerDeviceKind device_kind;
540 
541  switch (message) {
542  case kWmDpiChangedBeforeParent:
543  current_dpi_ = GetDpiForHWND(window_handle_);
544  OnDpiScale(current_dpi_);
545  return 0;
546  case WM_SIZE:
547  width = LOWORD(lparam);
548  height = HIWORD(lparam);
549 
550  current_width_ = width;
551  current_height_ = height;
552  HandleResize(width, height);
553 
554  OnWindowStateEvent(width == 0 && height == 0 ? WindowStateEvent::kHide
556  break;
557  case WM_PAINT:
558  OnPaint();
559  break;
560  case WM_TOUCH: {
561  UINT num_points = LOWORD(wparam);
562  touch_points_.resize(num_points);
563  auto touch_input_handle = reinterpret_cast<HTOUCHINPUT>(lparam);
564  if (GetTouchInputInfo(touch_input_handle, num_points,
565  touch_points_.data(), sizeof(TOUCHINPUT))) {
566  for (const auto& touch : touch_points_) {
567  // Generate a mapped ID for the Windows-provided touch ID
568  auto touch_id = touch_id_generator_.GetGeneratedId(touch.dwID);
569 
570  POINT pt = {TOUCH_COORD_TO_PIXEL(touch.x),
571  TOUCH_COORD_TO_PIXEL(touch.y)};
572  ScreenToClient(window_handle_, &pt);
573  auto x = static_cast<double>(pt.x);
574  auto y = static_cast<double>(pt.y);
575 
576  if (touch.dwFlags & TOUCHEVENTF_DOWN) {
577  OnPointerDown(x, y, kFlutterPointerDeviceKindTouch, touch_id,
578  WM_LBUTTONDOWN);
579  } else if (touch.dwFlags & TOUCHEVENTF_MOVE) {
580  OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id, 0);
581  } else if (touch.dwFlags & TOUCHEVENTF_UP) {
582  OnPointerUp(x, y, kFlutterPointerDeviceKindTouch, touch_id,
583  WM_LBUTTONDOWN);
584  OnPointerLeave(x, y, kFlutterPointerDeviceKindTouch, touch_id);
585  touch_id_generator_.ReleaseNumber(touch.dwID);
586  }
587  }
588  CloseTouchInputHandle(touch_input_handle);
589  }
590  return 0;
591  }
592  case WM_MOUSEMOVE:
593  device_kind = GetFlutterPointerDeviceKind();
594  if (device_kind == kFlutterPointerDeviceKindMouse) {
595  TrackMouseLeaveEvent(window_handle_);
596 
597  xPos = GET_X_LPARAM(lparam);
598  yPos = GET_Y_LPARAM(lparam);
599  mouse_x_ = static_cast<double>(xPos);
600  mouse_y_ = static_cast<double>(yPos);
601 
602  int mods = 0;
603  if (wparam & MK_CONTROL) {
604  mods |= kControl;
605  }
606  if (wparam & MK_SHIFT) {
607  mods |= kShift;
608  }
609  OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId,
610  mods);
611  }
612  break;
613  case WM_MOUSELEAVE:
614  device_kind = GetFlutterPointerDeviceKind();
615  if (device_kind == kFlutterPointerDeviceKindMouse) {
616  OnPointerLeave(mouse_x_, mouse_y_, device_kind,
617  kDefaultPointerDeviceId);
618  }
619 
620  // Once the tracked event is received, the TrackMouseEvent function
621  // resets. Set to false to make sure it's called once mouse movement is
622  // detected again.
623  tracking_mouse_leave_ = false;
624  break;
625  case WM_SETCURSOR: {
626  UINT hit_test_result = LOWORD(lparam);
627  if (hit_test_result == HTCLIENT) {
628  OnSetCursor();
629  return TRUE;
630  }
631  break;
632  }
633  case WM_SETFOCUS:
634  OnWindowStateEvent(WindowStateEvent::kFocus);
635  ::CreateCaret(window_handle_, nullptr, 1, 1);
636  break;
637  case WM_KILLFOCUS:
638  OnWindowStateEvent(WindowStateEvent::kUnfocus);
639  ::DestroyCaret();
640  break;
641  case WM_LBUTTONDOWN:
642  case WM_RBUTTONDOWN:
643  case WM_MBUTTONDOWN:
644  case WM_XBUTTONDOWN:
645  device_kind = GetFlutterPointerDeviceKind();
646  if (device_kind != kFlutterPointerDeviceKindMouse) {
647  break;
648  }
649 
650  if (message == WM_LBUTTONDOWN) {
651  // Capture the pointer in case the user drags outside the client area.
652  // In this case, the "mouse leave" event is delayed until the user
653  // releases the button. It's only activated on left click given that
654  // it's more common for apps to handle dragging with only the left
655  // button.
656  SetCapture(window_handle_);
657  }
658  button_pressed = message;
659  if (message == WM_XBUTTONDOWN) {
660  button_pressed = GET_XBUTTON_WPARAM(wparam);
661  }
662  xPos = GET_X_LPARAM(lparam);
663  yPos = GET_Y_LPARAM(lparam);
664  OnPointerDown(static_cast<double>(xPos), static_cast<double>(yPos),
665  device_kind, kDefaultPointerDeviceId, button_pressed);
666  break;
667  case WM_LBUTTONUP:
668  case WM_RBUTTONUP:
669  case WM_MBUTTONUP:
670  case WM_XBUTTONUP:
671  device_kind = GetFlutterPointerDeviceKind();
672  if (device_kind != kFlutterPointerDeviceKindMouse) {
673  break;
674  }
675 
676  if (message == WM_LBUTTONUP) {
677  ReleaseCapture();
678  }
679  button_pressed = message;
680  if (message == WM_XBUTTONUP) {
681  button_pressed = GET_XBUTTON_WPARAM(wparam);
682  }
683  xPos = GET_X_LPARAM(lparam);
684  yPos = GET_Y_LPARAM(lparam);
685  OnPointerUp(static_cast<double>(xPos), static_cast<double>(yPos),
686  device_kind, kDefaultPointerDeviceId, button_pressed);
687  break;
688  case WM_MOUSEWHEEL:
689  OnScroll(0.0,
690  -(static_cast<short>(HIWORD(wparam)) /
691  static_cast<double>(WHEEL_DELTA)),
692  kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId);
693  break;
694  case WM_MOUSEHWHEEL:
695  OnScroll((static_cast<short>(HIWORD(wparam)) /
696  static_cast<double>(WHEEL_DELTA)),
697  0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId);
698  break;
699  case WM_GETOBJECT: {
700  LRESULT lresult = OnGetObject(message, wparam, lparam);
701  if (lresult) {
702  return lresult;
703  }
704  break;
705  }
706  case WM_TIMER:
707  if (wparam == kDirectManipulationTimer) {
708  direct_manipulation_owner_->Update();
709  return 0;
710  }
711  break;
712  case DM_POINTERHITTEST: {
713  if (direct_manipulation_owner_) {
714  UINT contact_id = GET_POINTERID_WPARAM(wparam);
715  POINTER_INPUT_TYPE pointer_type;
716  if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) &&
717  pointer_type == PT_TOUCHPAD) {
718  direct_manipulation_owner_->SetContact(contact_id);
719  }
720  }
721  break;
722  }
723  case WM_INPUTLANGCHANGE:
724  // TODO(cbracken): pass this to TextInputManager to aid with
725  // language-specific issues.
726  break;
727  case WM_IME_SETCONTEXT:
728  OnImeSetContext(message, wparam, lparam);
729  // Strip the ISC_SHOWUICOMPOSITIONWINDOW bit from lparam before passing it
730  // to DefWindowProc() so that the composition window is hidden since
731  // Flutter renders the composing string itself.
732  result_lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
733  break;
734  case WM_IME_STARTCOMPOSITION:
735  OnImeStartComposition(message, wparam, lparam);
736  // Suppress further processing by DefWindowProc() so that the default
737  // system IME style isn't used, but rather the one set in the
738  // WM_IME_SETCONTEXT handler.
739  return TRUE;
740  case WM_IME_COMPOSITION:
741  OnImeComposition(message, wparam, lparam);
742  if (lparam & GCS_RESULTSTR || lparam & GCS_COMPSTR) {
743  // Suppress further processing by DefWindowProc() since otherwise it
744  // will emit the result string as WM_CHAR messages on commit. Instead,
745  // committing the composing text to the EditableText string is handled
746  // in TextInputModel::CommitComposing, triggered by
747  // OnImeEndComposition().
748  return TRUE;
749  }
750  break;
751  case WM_IME_ENDCOMPOSITION:
752  OnImeEndComposition(message, wparam, lparam);
753  return TRUE;
754  case WM_IME_REQUEST:
755  OnImeRequest(message, wparam, lparam);
756  break;
757  case WM_UNICHAR: {
758  // Tell third-pary app, we can support Unicode.
759  if (wparam == UNICODE_NOCHAR)
760  return TRUE;
761  // DefWindowProc will send WM_CHAR for this WM_UNICHAR.
762  break;
763  }
764  case WM_THEMECHANGED:
765  OnThemeChange();
766  break;
767  case WM_DEADCHAR:
768  case WM_SYSDEADCHAR:
769  case WM_CHAR:
770  case WM_SYSCHAR:
771  case WM_KEYDOWN:
772  case WM_SYSKEYDOWN:
773  case WM_KEYUP:
774  case WM_SYSKEYUP:
775  if (keyboard_manager_->HandleMessage(message, wparam, lparam)) {
776  return 0;
777  }
778  break;
779  }
780 
781  return Win32DefWindowProc(window_handle_, message, wparam, result_lparam);
782 }
783 
785  WPARAM const wparam,
786  LPARAM const lparam) {
787  LRESULT reference_result = static_cast<LRESULT>(0L);
788 
789  // Only the lower 32 bits of lparam are valid when checking the object id
790  // because it sometimes gets sign-extended incorrectly (but not always).
791  DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lparam));
792 
793  bool is_uia_request = static_cast<DWORD>(UiaRootObjectId) == obj_id;
794  bool is_msaa_request = static_cast<DWORD>(OBJID_CLIENT) == obj_id;
795 
796  if (is_uia_request || is_msaa_request) {
797  // On Windows, we don't get a notification that the screen reader has been
798  // enabled or disabled. There is an API to query for screen reader state,
799  // but that state isn't set by all screen readers, including by Narrator,
800  // the screen reader that ships with Windows:
801  // https://docs.microsoft.com/en-us/windows/win32/winauto/screen-reader-parameter
802  //
803  // Instead, we enable semantics in Flutter if Windows issues queries for
804  // Microsoft Active Accessibility (MSAA) COM objects.
806  }
807 
808  gfx::NativeViewAccessible root_view = GetNativeViewAccessible();
809  // TODO(schectman): UIA is currently disabled by default.
810  // https://github.com/flutter/flutter/issues/114547
811  if (root_view) {
812  CreateAxFragmentRoot();
813  if (is_uia_request) {
814 #ifdef FLUTTER_ENGINE_USE_UIA
815  // Retrieve UIA object for the root view.
816  Microsoft::WRL::ComPtr<IRawElementProviderSimple> root;
817  if (SUCCEEDED(
818  ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
819  IID_PPV_ARGS(&root)))) {
820  // Return the UIA object via UiaReturnRawElementProvider(). See:
821  // https://docs.microsoft.com/en-us/windows/win32/winauto/wm-getobject
822  reference_result = UiaReturnRawElementProvider(window_handle_, wparam,
823  lparam, root.Get());
824  } else {
825  FML_LOG(ERROR) << "Failed to query AX fragment root.";
826  }
827 #endif // FLUTTER_ENGINE_USE_UIA
828  } else if (is_msaa_request) {
829  // Create the accessibility root if it does not already exist.
830  // Return the IAccessible for the root view.
831  Microsoft::WRL::ComPtr<IAccessible> root;
832  ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
833  IID_PPV_ARGS(&root));
834  reference_result = LresultFromObject(IID_IAccessible, wparam, root.Get());
835  }
836  }
837  return reference_result;
838 }
839 
841  WPARAM const wparam,
842  LPARAM const lparam) {
843  if (wparam != 0) {
844  text_input_manager_->CreateImeWindow();
845  }
846 }
847 
849  WPARAM const wparam,
850  LPARAM const lparam) {
851  text_input_manager_->CreateImeWindow();
852  OnComposeBegin();
853 }
854 
856  WPARAM const wparam,
857  LPARAM const lparam) {
858  // Update the IME window position.
859  text_input_manager_->UpdateImeWindow();
860 
861  if (lparam == 0) {
862  OnComposeChange(u"", 0);
863  OnComposeCommit();
864  }
865 
866  // Process GCS_RESULTSTR at fisrt, because Google Japanese Input and ATOK send
867  // both GCS_RESULTSTR and GCS_COMPSTR to commit composed text and send new
868  // composing text.
869  if (lparam & GCS_RESULTSTR) {
870  // Commit but don't end composing.
871  // Read the committed composing string.
872  long pos = text_input_manager_->GetComposingCursorPosition();
873  std::optional<std::u16string> text = text_input_manager_->GetResultString();
874  if (text) {
875  OnComposeChange(text.value(), pos);
876  OnComposeCommit();
877  }
878  }
879  if (lparam & GCS_COMPSTR) {
880  // Read the in-progress composing string.
881  long pos = text_input_manager_->GetComposingCursorPosition();
882  std::optional<std::u16string> text =
883  text_input_manager_->GetComposingString();
884  if (text) {
885  OnComposeChange(text.value(), pos);
886  }
887  }
888 }
889 
891  WPARAM const wparam,
892  LPARAM const lparam) {
893  text_input_manager_->DestroyImeWindow();
894  OnComposeEnd();
895 }
896 
898  WPARAM const wparam,
899  LPARAM const lparam) {
900  // TODO(cbracken): Handle IMR_RECONVERTSTRING, IMR_DOCUMENTFEED,
901  // and IMR_QUERYCHARPOSITION messages.
902  // https://github.com/flutter/flutter/issues/74547
903 }
904 
906  text_input_manager_->AbortComposing();
907 }
908 
910  text_input_manager_->UpdateCaretRect(rect);
911 }
912 
914  return current_dpi_;
915 }
916 
918  return current_width_;
919 }
920 
922  return current_height_;
923 }
924 
926  return scroll_offset_multiplier_;
927 }
928 
930  UINT Msg,
931  WPARAM wParam,
932  LPARAM lParam) {
933  return ::DefWindowProc(hWnd, Msg, wParam, lParam);
934 }
935 
936 void FlutterWindow::Destroy() {
937  if (window_handle_) {
938  text_input_manager_->SetWindowHandle(nullptr);
939  DestroyWindow(window_handle_);
940  window_handle_ = nullptr;
941  }
942 
943  UnregisterClass(window_class_name_.c_str(), nullptr);
944 }
945 
946 void FlutterWindow::CreateAxFragmentRoot() {
947  if (ax_fragment_root_) {
948  return;
949  }
950  ax_fragment_root_ = std::make_unique<ui::AXFragmentRootWin>(
951  window_handle_, GetAxFragmentRootDelegate());
953  std::make_unique<AlertPlatformNodeDelegate>(*ax_fragment_root_);
954  ui::AXPlatformNode* alert_node =
955  ui::AXPlatformNodeWin::Create(alert_delegate_.get());
956  alert_node_.reset(static_cast<ui::AXPlatformNodeWin*>(alert_node));
957  ax_fragment_root_->SetAlertNode(alert_node_.get());
958 }
959 
960 } // namespace flutter
virtual void OnCursorRectUpdated(const Rect &rect) override
virtual void OnPointerLeave(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id)
virtual float GetScrollOffsetMultiplier()
virtual ui::AXPlatformNodeWin * GetAlert() override
virtual void OnText(const std::u16string &text) override
virtual UINT Win32DispatchMessage(UINT Msg, WPARAM wParam, LPARAM lParam) override
virtual BOOL Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) override
virtual void OnThemeChange()
std::unique_ptr< AlertPlatformNodeDelegate > alert_delegate_
virtual bool OnBitmapSurfaceUpdated(const void *allocation, size_t row_bytes, size_t height) override
virtual void OnImeRequest(UINT const message, WPARAM const wparam, LPARAM const lparam)
virtual LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
virtual void OnImeStartComposition(UINT const message, WPARAM const wparam, LPARAM const lparam)
std::wstring NarrowToWide(const char *source)
virtual void OnSetCursor()
virtual ui::AXFragmentRootDelegateWin * GetAxFragmentRootDelegate()
virtual void OnScroll(double delta_x, double delta_y, FlutterPointerDeviceKind device_kind, int32_t device_id)
virtual void OnPointerUp(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, UINT button)
void InitializeChild(const char *title, unsigned int width, unsigned int height)
virtual AlertPlatformNodeDelegate * GetAlertDelegate() override
virtual void SetFlutterCursor(HCURSOR cursor) override
std::unique_ptr< DirectManipulationOwner > direct_manipulation_owner_
virtual HWND GetWindowHandle() override
virtual void OnPointerDown(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, UINT button)
virtual void OnComposeCommit()
virtual void UpdateCursorRect(const Rect &rect)
virtual PhysicalWindowBounds GetPhysicalWindowBounds() override
virtual void OnImeSetContext(UINT const message, WPARAM const wparam, LPARAM const lparam)
virtual void OnWindowStateEvent(WindowStateEvent event)
virtual void OnKey(int key, int scancode, int action, char32_t character, bool extended, bool was_down, KeyEventCallback callback) override
virtual void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, int modifiers_state)
std::unique_ptr< ui::AXPlatformNodeWin > alert_node_
virtual PointerLocation GetPrimaryPointerLocation() override
virtual void OnComposeEnd()
virtual void SetView(WindowBindingHandlerDelegate *view) override
LRESULT HandleMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
virtual void OnComposeChange(const std::u16string &text, int cursor_pos)
virtual void OnResetImeComposing() override
virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) override
virtual void OnImeComposition(UINT const message, WPARAM const wparam, LPARAM const lparam)
virtual void OnDpiScale(unsigned int dpi)
virtual void AbortImeComposing()
virtual float GetDpiScale() override
virtual void OnUpdateSemanticsEnabled(bool enabled)
virtual LRESULT OnGetObject(UINT const message, WPARAM const wparam, LPARAM const lparam)
virtual gfx::NativeViewAccessible GetNativeViewAccessible()
virtual void OnComposeBegin()
virtual bool OnBitmapSurfaceCleared() override
virtual void OnResize(unsigned int width, unsigned int height)
virtual void UpdateFlutterCursor(const std::string &cursor_name) override
virtual void OnImeEndComposition(UINT const message, WPARAM const wparam, LPARAM const lparam)
std::function< void(bool)> KeyEventCallback
double top() const
Definition: geometry.h:64
double height() const
Definition: geometry.h:68
double left() const
Definition: geometry.h:63
double width() const
Definition: geometry.h:67
virtual void OnPointerDown(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, FlutterPointerMouseButtons button)=0
virtual void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, int modifiers_state)=0
virtual void OnComposeChange(const std::u16string &text, int cursor_pos)=0
virtual void OnText(const std::u16string &)=0
virtual void OnPointerLeave(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id)=0
virtual void OnKey(int key, int scancode, int action, char32_t character, bool extended, bool was_down, KeyEventCallback callback)=0
virtual void OnUpdateSemanticsEnabled(bool enabled)=0
virtual void OnPointerUp(double x, double y, FlutterPointerDeviceKind device_kind, int32_t device_id, FlutterPointerMouseButtons button)=0
virtual void OnWindowStateEvent(HWND hwnd, WindowStateEvent event)=0
virtual ui::AXFragmentRootDelegateWin * GetAxFragmentRootDelegate()=0
virtual gfx::NativeViewAccessible GetNativeViewAccessible()=0
virtual bool OnWindowSizeChanged(size_t width, size_t height)=0
virtual void OnScroll(double x, double y, double delta_x, double delta_y, int scroll_offset_multiplier, FlutterPointerDeviceKind device_kind, int32_t device_id)=0
FlutterDesktopBinaryReply callback
std::u16string text
Win32Message message
constexpr int kShift
UINT GetDpiForHWND(HWND hwnd)
Definition: dpi_utils.cc:130
WindowStateEvent
An event representing a change in window state that may update the.
constexpr int kControl