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