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