#include "flutter_window.h" #include #include #include "flutter/generated_plugin_registrant.h" #include "ocr_channel.h" #include "pen_channel.h" // SetWindowSubclass / DefSubclassProc / RemoveWindowSubclass live in comctl32. #pragma comment(lib, "comctl32.lib") namespace { // Id for the pen observer subclass installed on the Flutter child view window. constexpr UINT_PTR kPenSubclassId = 1; // Subclass proc on the FLUTTER CHILD window. WM_POINTER messages are delivered // to the window under the pointer — that is the Flutter view's child HWND, not // the top-level window — so this is where the pen's barrel/eraser/tilt actually // arrive. We observe (non-consuming) and pass every message straight through to // Flutter's own handler via DefSubclassProc, so input behavior is unchanged. LRESULT CALLBACK FlutterChildSubclassProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, UINT_PTR /*subclass_id*/, DWORD_PTR /*ref_data*/) { ObservePenMessage(message, wparam, lparam); return DefSubclassProc(hwnd, message, wparam, lparam); } // Turn off the OS-drawn pen visual feedback (the "Windows Ink" tap ripple, the // press-and-hold right-click ring, barrel feedback, and flicks) so the pen // draws instantly with no ugly OS animation under the nib. The tablet input // service reads these flags from a window property; set it on whichever window // receives the pointer input. Flag values are from the Windows pen/tablet docs // (not declared in the public SDK headers). void DisablePenVisualFeedback(HWND hwnd) { if (!hwnd) { return; } constexpr ULONG_PTR kDisablePressAndHold = 0x00000001; constexpr ULONG_PTR kDisablePenTapFeedback = 0x00000008; constexpr ULONG_PTR kDisablePenBarrelFeedback = 0x00000010; constexpr ULONG_PTR kDisableTouchUIForceOn = 0x00000100; constexpr ULONG_PTR kDisableTouchUIForceOff = 0x00000200; constexpr ULONG_PTR kDisableFlicks = 0x00010000; const ULONG_PTR flags = kDisablePressAndHold | kDisablePenTapFeedback | kDisablePenBarrelFeedback | kDisableTouchUIForceOn | kDisableTouchUIForceOff | kDisableFlicks; const ATOM atom = ::GlobalAddAtomW(L"MicrosoftTabletPenServiceProperty"); if (atom != 0) { ::SetPropW(hwnd, MAKEINTATOM(atom), reinterpret_cast(flags)); ::GlobalDeleteAtom(atom); } } } // namespace FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); RegisterOcrChannel(flutter_controller_->engine()); RegisterPenChannel(flutter_controller_->engine()); HWND flutter_child = flutter_controller_->view()->GetNativeWindow(); SetChildContent(flutter_child); // Observe WM_POINTER on the Flutter child window (where pen messages land) so // the pen barrel/eraser/inverted flags + tilt reach the badnote/pen channel. if (flutter_child) { SetWindowSubclass(flutter_child, FlutterChildSubclassProc, kPenSubclassId, 0); } // Kill the OS pen tap/press-and-hold visual feedback on both the top-level // window and the Flutter child (the one that actually gets the pointer input). DisablePenVisualFeedback(GetHandle()); DisablePenVisualFeedback(flutter_child); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { HWND flutter_child = flutter_controller_->view()->GetNativeWindow(); if (flutter_child) { RemoveWindowSubclass(flutter_child, FlutterChildSubclassProc, kPenSubclassId); } flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Observe pen messages before Flutter consumes WM_POINTER events. ObservePenMessage(message, wparam, lparam); // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); }