fix(windows): forward touch input over video

This commit is contained in:
edde746
2026-07-13 18:48:58 +02:00
parent b0c2827eb4
commit dfeffad044
+42 -12
View File
@@ -49,18 +49,47 @@ flutter::EncodableValue NodeToEncodableValue(const mpv_node* node) {
} }
// DComp-mode input forwarding. mpv's inner window lives on mpv's own thread // DComp-mode input forwarding. mpv's inner window lives on mpv's own thread
// and consumes the mouse input over the video (WS_EX_TRANSPARENT hit-test // and consumes input over the video (WS_EX_TRANSPARENT hit-test skipping is
// skipping is same-thread-only, and disabling the subtree makes the system // same-thread-only, and disabling the subtree makes the system drop the input
// drop the input entirely instead of routing it to a sibling). Subclass the // entirely instead of routing it to a sibling). Subclass the inner window
// inner window (legal within one process, even across threads) and forward // (legal within one process, even across threads) and forward mouse and pointer
// mouse input to the Flutter view with coordinates translated into view // input to the Flutter view. Pointer messages must be sent synchronously:
// space. The subclass proc runs on mpv's thread and only uses thread-safe // Flutter calls GetPointerInfo while handling them, and Windows only retains
// calls (PostMessage / MapWindowPoints / CallWindowProc). // that data for the current or forwarded message.
constexpr bool IsFlutterPointerMessage(UINT message) {
switch (message) {
case WM_POINTERDOWN:
case WM_POINTERUPDATE:
case WM_POINTERUP:
case WM_POINTERLEAVE:
return true;
default:
return false;
}
}
static_assert(IsFlutterPointerMessage(WM_POINTERDOWN));
static_assert(IsFlutterPointerMessage(WM_POINTERUPDATE));
static_assert(IsFlutterPointerMessage(WM_POINTERUP));
static_assert(IsFlutterPointerMessage(WM_POINTERLEAVE));
static_assert(!IsFlutterPointerMessage(WM_MOUSEMOVE));
WNDPROC g_mpv_inner_original_proc = nullptr; WNDPROC g_mpv_inner_original_proc = nullptr;
HWND g_mpv_inner_hwnd = nullptr; HWND g_mpv_inner_hwnd = nullptr;
HWND g_forward_target_view = nullptr; HWND g_forward_target_view = nullptr;
LRESULT CALLBACK MpvInnerSubclassProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { LRESULT CALLBACK MpvInnerSubclassProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
if (IsFlutterPointerMessage(message)) {
HWND view = g_forward_target_view;
if (view) {
// WM_POINTER coordinates are already in screen space. SendMessage also
// preserves the message association required by GetPointerInfo in the
// Flutter view's window procedure.
::SendMessageW(view, message, wparam, lparam);
}
return 0;
}
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) { if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
HWND view = g_forward_target_view; HWND view = g_forward_target_view;
if (view) { if (view) {
@@ -125,9 +154,10 @@ bool MpvPlayer::Initialize(HWND view) {
// |view|. The video child then sits in the view's own per-window layer // |view|. The video child then sits in the view's own per-window layer
// stack, above the view's (never-painted) layer-1 content and below the // stack, above the view's (never-painted) layer-1 content and below the
// engine's topmost DComp visual carrying the UI. WS_CLIPSIBLINGS keeps it // engine's topmost DComp visual carrying the UI. WS_CLIPSIBLINGS keeps it
// from painting over neighboring view children. Mouse input over the video // from painting over neighboring view children. Mouse, touch, and stylus
// is delivered to mpv's own inner window (on mpv's thread); the subclass // input over the video is delivered to mpv's own inner window (on mpv's
// installed in EnsureMpvInnerSubclassed forwards it back to the view. // thread); the subclass installed in EnsureMpvInnerSubclassed forwards it
// back to the Flutter view.
hwnd_ = ::CreateWindowExW( hwnd_ = ::CreateWindowExW(
WS_EX_NOPARENTNOTIFY, L"STATIC", L"", WS_CHILD | WS_CLIPSIBLINGS, 0, 0, 100, 100, view, nullptr, WS_EX_NOPARENTNOTIFY, L"STATIC", L"", WS_CHILD | WS_CLIPSIBLINGS, 0, 0, 100, 100, view, nullptr,
GetModuleHandle(nullptr), nullptr); GetModuleHandle(nullptr), nullptr);
@@ -319,8 +349,8 @@ void MpvPlayer::SetRect(RECT rect, double device_pixel_ratio) {
::SetWindowPos(hwnd_, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE); ::SetWindowPos(hwnd_, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE);
// mpv creates its inner window lazily on its own thread; subclass it (and // mpv creates its inner window lazily on its own thread; subclass it (and
// re-subclass if mpv ever recreates it) so mouse input over the video is // re-subclass if mpv ever recreates it) so mouse and pointer input over the
// forwarded to the Flutter view. // video is forwarded to the Flutter view.
EnsureMpvInnerSubclassed(hwnd_); EnsureMpvInnerSubclassed(hwnd_);
} }