13 #include "flutter/fml/logging.h"
14 #include "flutter/shell/platform/embedder/embedder.h"
26 constexpr
int base_dpi = 96;
28 static const int kMinTouchDeviceId = 0;
29 static const int kMaxTouchDeviceId = 128;
31 static const int kLinesPerScrollWindowsDefault = 3;
39 static HCURSOR GetCursorByName(
const std::string& cursor_name) {
40 static auto* cursors =
new std::map<std::string, const wchar_t*>{
41 {
"allScroll", IDC_SIZEALL},
44 {
"forbidden", IDC_NO},
46 {
"move", IDC_SIZEALL},
49 {
"precise", IDC_CROSS},
50 {
"progress", IDC_APPSTARTING},
52 {
"resizeColumn", IDC_SIZEWE},
53 {
"resizeDown", IDC_SIZENS},
54 {
"resizeDownLeft", IDC_SIZENESW},
55 {
"resizeDownRight", IDC_SIZENWSE},
56 {
"resizeLeft", IDC_SIZEWE},
57 {
"resizeLeftRight", IDC_SIZEWE},
58 {
"resizeRight", IDC_SIZEWE},
59 {
"resizeRow", IDC_SIZENS},
60 {
"resizeUp", IDC_SIZENS},
61 {
"resizeUpDown", IDC_SIZENS},
62 {
"resizeUpLeft", IDC_SIZENWSE},
63 {
"resizeUpRight", IDC_SIZENESW},
64 {
"resizeUpLeftDownRight", IDC_SIZENWSE},
65 {
"resizeUpRightDownLeft", IDC_SIZENESW},
68 const wchar_t* idc_name = IDC_ARROW;
69 auto it = cursors->find(cursor_name);
70 if (it != cursors->end()) {
71 idc_name = it->second;
73 return ::LoadCursor(
nullptr, idc_name);
76 static constexpr int32_t kDefaultPointerDeviceId = 0;
82 static FlutterPointerDeviceKind GetFlutterPointerDeviceKind() {
83 constexpr LPARAM kTouchOrPenSignature = 0xFF515700;
84 constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80;
85 constexpr LPARAM kSignatureMask = 0xFFFFFF00;
86 LPARAM info = GetMessageExtraInfo();
87 if ((info & kSignatureMask) == kTouchOrPenSignature) {
88 if ((info & kTouchSignature) == kTouchSignature) {
89 return kFlutterPointerDeviceKindTouch;
91 return kFlutterPointerDeviceKindStylus;
93 return kFlutterPointerDeviceKindMouse;
97 static uint64_t ConvertWinButtonToFlutterButton(UINT button) {
101 return kFlutterPointerButtonMousePrimary;
104 return kFlutterPointerButtonMouseSecondary;
107 return kFlutterPointerButtonMouseMiddle;
109 return kFlutterPointerButtonMouseBack;
111 return kFlutterPointerButtonMouseForward;
113 FML_LOG(WARNING) <<
"Mouse button not recognized: " << button;
122 std::shared_ptr<WindowsProcTable> windows_proc_table,
123 std::unique_ptr<TextInputManager> text_input_manager)
124 : binding_handler_delegate_(nullptr),
125 touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId),
126 windows_proc_table_(std::move(windows_proc_table)),
127 text_input_manager_(std::move(text_input_manager)),
128 ax_fragment_root_(nullptr) {
137 UpdateScrollOffsetMultiplier();
139 if (windows_proc_table_ ==
nullptr) {
140 windows_proc_table_ = std::make_unique<WindowsProcTable>();
142 if (text_input_manager_ ==
nullptr) {
143 text_input_manager_ = std::make_unique<TextInputManager>();
145 keyboard_manager_ = std::make_unique<KeyboardManager>(
this);
148 current_cursor_ = ::LoadCursor(
nullptr, IDC_ARROW);
157 binding_handler_delegate_ = window;
159 if (restored_ && window) {
162 if (focused_ && window) {
168 return static_cast<float>(
GetCurrentDPI()) /
static_cast<float>(base_dpi);
180 current_cursor_ = GetCursorByName(cursor_name);
184 current_cursor_ = cursor;
185 ::SetCursor(current_cursor_);
193 if (binding_handler_delegate_ !=
nullptr) {
199 if (binding_handler_delegate_ !=
nullptr) {
206 FlutterPointerDeviceKind device_kind,
208 int modifiers_state) {
209 binding_handler_delegate_->
OnPointerMove(x, y, device_kind, device_id,
215 FlutterPointerDeviceKind device_kind,
218 uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);
219 if (flutter_button != 0) {
221 x, y, device_kind, device_id,
222 static_cast<FlutterPointerMouseButtons
>(flutter_button));
228 FlutterPointerDeviceKind device_kind,
231 uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);
232 if (flutter_button != 0) {
234 x, y, device_kind, device_id,
235 static_cast<FlutterPointerMouseButtons
>(flutter_button));
241 FlutterPointerDeviceKind device_kind,
243 binding_handler_delegate_->
OnPointerLeave(x, y, device_kind, device_id);
247 ::SetCursor(current_cursor_);
288 FlutterPointerDeviceKind device_kind,
291 GetCursorPos(&point);
294 binding_handler_delegate_->
OnScroll(point.x, point.y, delta_x, delta_y,
316 bmi.bmiHeader.biSize =
sizeof(BITMAPINFOHEADER);
317 bmi.bmiHeader.biWidth = row_bytes / 4;
318 bmi.bmiHeader.biHeight = -height;
319 bmi.bmiHeader.biPlanes = 1;
320 bmi.bmiHeader.biBitCount = 32;
321 bmi.bmiHeader.biCompression = BI_RGB;
322 bmi.bmiHeader.biSizeImage = 0;
323 int ret = SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0, height,
324 allocation, &bmi, DIB_RGB_COLORS);
330 if (binding_handler_delegate_ ==
nullptr) {
339 GetCursorPos(&point);
341 return {(size_t)point.x, (
size_t)point.y};
353 CreateAxFragmentRoot();
358 CreateAxFragmentRoot();
379 if (hwnd && binding_handler_delegate_) {
384 void FlutterWindow::TrackMouseLeaveEvent(HWND hwnd) {
385 if (!tracking_mouse_leave_) {
387 tme.cbSize =
sizeof(tme);
388 tme.hwndTrack = hwnd;
389 tme.dwFlags = TME_LEAVE;
390 TrackMouseEvent(&tme);
391 tracking_mouse_leave_ =
true;
395 void FlutterWindow::HandleResize(UINT width, UINT height) {
396 current_width_ = width;
397 current_height_ = height;
404 FlutterWindow* FlutterWindow::GetThisFromHandle(HWND
const window) noexcept {
405 return reinterpret_cast<FlutterWindow*
>(
406 GetWindowLongPtr(window, GWLP_USERDATA));
409 void FlutterWindow::UpdateScrollOffsetMultiplier() {
410 UINT lines_per_scroll = kLinesPerScrollWindowsDefault;
413 SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_scroll, 0);
417 scroll_offset_multiplier_ =
418 static_cast<float>(lines_per_scroll) * 100.0 / 3.0;
423 unsigned int height) {
427 WNDCLASS window_class = RegisterWindowClass(converted_title);
429 auto* result = CreateWindowEx(
430 0, window_class.lpszClassName, converted_title.c_str(),
431 WS_CHILD | WS_VISIBLE, CW_DEFAULT, CW_DEFAULT, width, height,
432 HWND_MESSAGE,
nullptr, window_class.hInstance,
this);
434 if (result ==
nullptr) {
435 auto error = GetLastError();
437 size_t size = FormatMessageW(
438 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
439 FORMAT_MESSAGE_IGNORE_INSERTS,
440 NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
441 reinterpret_cast<LPWSTR
>(&
message), 0, NULL);
445 SetUserObjectInformationA(GetCurrentProcess(),
446 UOI_TIMERPROC_EXCEPTION_SUPPRESSION, FALSE, 1);
451 SetTimer(result, kDirectManipulationTimer, 14,
nullptr);
457 return window_handle_;
464 return ::PeekMessage(lpMsg, window_handle_, wMsgFilterMin, wMsgFilterMax,
469 return ::MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR);
475 return ::SendMessage(window_handle_, Msg, wParam, lParam);
479 size_t length = strlen(source);
481 std::wstring wideTitle(length, L
'#');
482 mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length);
486 WNDCLASS FlutterWindow::RegisterWindowClass(std::wstring& title) {
487 window_class_name_ = title;
489 WNDCLASS window_class{};
490 window_class.hCursor = LoadCursor(
nullptr, IDC_ARROW);
491 window_class.lpszClassName = title.c_str();
492 window_class.style = CS_HREDRAW | CS_VREDRAW;
493 window_class.cbClsExtra = 0;
494 window_class.cbWndExtra = 0;
495 window_class.hInstance = GetModuleHandle(
nullptr);
496 window_class.hIcon =
nullptr;
497 window_class.hbrBackground = 0;
498 window_class.lpszMenuName =
nullptr;
499 window_class.lpfnWndProc = WndProc;
500 RegisterClass(&window_class);
504 LRESULT CALLBACK FlutterWindow::WndProc(HWND
const window,
507 LPARAM
const lparam) noexcept {
509 auto cs =
reinterpret_cast<CREATESTRUCT*
>(lparam);
510 SetWindowLongPtr(window, GWLP_USERDATA,
511 reinterpret_cast<LONG_PTR
>(cs->lpCreateParams));
513 auto that =
static_cast<FlutterWindow*
>(cs->lpCreateParams);
514 that->window_handle_ = window;
515 that->text_input_manager_->SetWindowHandle(window);
516 RegisterTouchWindow(window, 0);
517 }
else if (FlutterWindow* that = GetThisFromHandle(window)) {
518 return that->HandleMessage(
message, wparam, lparam);
521 return DefWindowProc(window,
message, wparam, lparam);
527 LPARAM
const lparam) noexcept {
528 LPARAM result_lparam = lparam;
529 int xPos = 0, yPos = 0;
530 UINT width = 0, height = 0;
531 UINT button_pressed = 0;
532 FlutterPointerDeviceKind device_kind;
535 case kWmDpiChangedBeforeParent:
537 OnDpiScale(current_dpi_);
540 width = LOWORD(lparam);
541 height = HIWORD(lparam);
543 current_width_ = width;
544 current_height_ = height;
545 HandleResize(width, height);
554 UINT num_points = LOWORD(wparam);
555 touch_points_.resize(num_points);
556 auto touch_input_handle =
reinterpret_cast<HTOUCHINPUT
>(lparam);
557 if (GetTouchInputInfo(touch_input_handle, num_points,
558 touch_points_.data(),
sizeof(TOUCHINPUT))) {
559 for (
const auto& touch : touch_points_) {
561 auto touch_id = touch_id_generator_.GetGeneratedId(touch.dwID);
563 POINT pt = {TOUCH_COORD_TO_PIXEL(touch.x),
564 TOUCH_COORD_TO_PIXEL(touch.y)};
565 ScreenToClient(window_handle_, &pt);
566 auto x =
static_cast<double>(pt.x);
567 auto y =
static_cast<double>(pt.y);
569 if (touch.dwFlags & TOUCHEVENTF_DOWN) {
570 OnPointerDown(x, y, kFlutterPointerDeviceKindTouch, touch_id,
572 }
else if (touch.dwFlags & TOUCHEVENTF_MOVE) {
573 OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id, 0);
574 }
else if (touch.dwFlags & TOUCHEVENTF_UP) {
575 OnPointerUp(x, y, kFlutterPointerDeviceKindTouch, touch_id,
577 OnPointerLeave(x, y, kFlutterPointerDeviceKindTouch, touch_id);
578 touch_id_generator_.ReleaseNumber(touch.dwID);
581 CloseTouchInputHandle(touch_input_handle);
586 device_kind = GetFlutterPointerDeviceKind();
587 if (device_kind == kFlutterPointerDeviceKindMouse) {
588 TrackMouseLeaveEvent(window_handle_);
590 xPos = GET_X_LPARAM(lparam);
591 yPos = GET_Y_LPARAM(lparam);
592 mouse_x_ =
static_cast<double>(xPos);
593 mouse_y_ =
static_cast<double>(yPos);
596 if (wparam & MK_CONTROL) {
599 if (wparam & MK_SHIFT) {
602 OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId,
607 device_kind = GetFlutterPointerDeviceKind();
608 if (device_kind == kFlutterPointerDeviceKindMouse) {
609 OnPointerLeave(mouse_x_, mouse_y_, device_kind,
610 kDefaultPointerDeviceId);
616 tracking_mouse_leave_ =
false;
619 UINT hit_test_result = LOWORD(lparam);
620 if (hit_test_result == HTCLIENT) {
628 ::CreateCaret(window_handle_,
nullptr, 1, 1);
638 device_kind = GetFlutterPointerDeviceKind();
639 if (device_kind != kFlutterPointerDeviceKindMouse) {
643 if (
message == WM_LBUTTONDOWN) {
649 SetCapture(window_handle_);
652 if (
message == WM_XBUTTONDOWN) {
653 button_pressed = GET_XBUTTON_WPARAM(wparam);
655 xPos = GET_X_LPARAM(lparam);
656 yPos = GET_Y_LPARAM(lparam);
657 OnPointerDown(
static_cast<double>(xPos),
static_cast<double>(yPos),
658 device_kind, kDefaultPointerDeviceId, button_pressed);
664 device_kind = GetFlutterPointerDeviceKind();
665 if (device_kind != kFlutterPointerDeviceKindMouse) {
674 button_pressed = GET_XBUTTON_WPARAM(wparam);
676 xPos = GET_X_LPARAM(lparam);
677 yPos = GET_Y_LPARAM(lparam);
678 OnPointerUp(
static_cast<double>(xPos),
static_cast<double>(yPos),
679 device_kind, kDefaultPointerDeviceId, button_pressed);
683 -(
static_cast<short>(HIWORD(wparam)) /
684 static_cast<double>(WHEEL_DELTA)),
685 kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId);
688 OnScroll((
static_cast<short>(HIWORD(wparam)) /
689 static_cast<double>(WHEEL_DELTA)),
690 0.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId);
693 LRESULT lresult = OnGetObject(
message, wparam, lparam);
700 if (wparam == kDirectManipulationTimer) {
701 direct_manipulation_owner_->Update();
705 case DM_POINTERHITTEST: {
706 if (direct_manipulation_owner_) {
707 UINT contact_id = GET_POINTERID_WPARAM(wparam);
708 POINTER_INPUT_TYPE pointer_type;
709 if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) &&
710 pointer_type == PT_TOUCHPAD) {
711 direct_manipulation_owner_->SetContact(contact_id);
716 case WM_INPUTLANGCHANGE:
720 case WM_IME_SETCONTEXT:
721 OnImeSetContext(
message, wparam, lparam);
725 result_lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
727 case WM_IME_STARTCOMPOSITION:
728 OnImeStartComposition(
message, wparam, lparam);
733 case WM_IME_COMPOSITION:
734 OnImeComposition(
message, wparam, lparam);
735 if (lparam & GCS_RESULTSTR || lparam & GCS_COMPSTR) {
744 case WM_IME_ENDCOMPOSITION:
745 OnImeEndComposition(
message, wparam, lparam);
748 OnImeRequest(
message, wparam, lparam);
752 if (wparam == UNICODE_NOCHAR)
757 case WM_THEMECHANGED:
768 if (keyboard_manager_->HandleMessage(
message, wparam, lparam)) {
774 return Win32DefWindowProc(window_handle_,
message, wparam, result_lparam);
779 LPARAM
const lparam) {
780 LRESULT reference_result =
static_cast<LRESULT
>(0L);
784 DWORD obj_id =
static_cast<DWORD
>(
static_cast<DWORD_PTR
>(lparam));
786 bool is_uia_request =
static_cast<DWORD
>(UiaRootObjectId) == obj_id;
787 bool is_msaa_request =
static_cast<DWORD
>(OBJID_CLIENT) == obj_id;
789 if (is_uia_request || is_msaa_request) {
805 CreateAxFragmentRoot();
806 if (is_uia_request) {
807 #ifdef FLUTTER_ENGINE_USE_UIA
809 Microsoft::WRL::ComPtr<IRawElementProviderSimple> root;
811 ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
812 IID_PPV_ARGS(&root)))) {
815 reference_result = UiaReturnRawElementProvider(window_handle_, wparam,
818 FML_LOG(ERROR) <<
"Failed to query AX fragment root.";
820 #endif // FLUTTER_ENGINE_USE_UIA
821 }
else if (is_msaa_request) {
824 Microsoft::WRL::ComPtr<IAccessible> root;
825 ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
826 IID_PPV_ARGS(&root));
827 reference_result = LresultFromObject(IID_IAccessible, wparam, root.Get());
830 return reference_result;
835 LPARAM
const lparam) {
837 text_input_manager_->CreateImeWindow();
843 LPARAM
const lparam) {
844 text_input_manager_->CreateImeWindow();
850 LPARAM
const lparam) {
852 text_input_manager_->UpdateImeWindow();
862 if (lparam & GCS_RESULTSTR) {
865 long pos = text_input_manager_->GetComposingCursorPosition();
866 std::optional<std::u16string>
text = text_input_manager_->GetResultString();
872 if (lparam & GCS_COMPSTR) {
874 long pos = text_input_manager_->GetComposingCursorPosition();
875 std::optional<std::u16string>
text =
876 text_input_manager_->GetComposingString();
885 LPARAM
const lparam) {
886 text_input_manager_->DestroyImeWindow();
892 LPARAM
const lparam) {
899 text_input_manager_->AbortComposing();
903 text_input_manager_->UpdateCaretRect(rect);
911 return current_width_;
915 return current_height_;
919 return scroll_offset_multiplier_;
926 return ::DefWindowProc(hWnd, Msg, wParam, lParam);
929 void FlutterWindow::Destroy() {
930 if (window_handle_) {
931 text_input_manager_->SetWindowHandle(
nullptr);
932 DestroyWindow(window_handle_);
933 window_handle_ =
nullptr;
936 UnregisterClass(window_class_name_.c_str(),
nullptr);
939 void FlutterWindow::CreateAxFragmentRoot() {
940 if (ax_fragment_root_) {
943 ax_fragment_root_ = std::make_unique<ui::AXFragmentRootWin>(
946 std::make_unique<AlertPlatformNodeDelegate>(*ax_fragment_root_);
947 ui::AXPlatformNode* alert_node =
949 alert_node_.reset(
static_cast<ui::AXPlatformNodeWin*
>(alert_node));
950 ax_fragment_root_->SetAlertNode(
alert_node_.get());