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