From bc00a16ddbac0d8bb672b8c29a2262e20f186cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=97=AD?= Date: Thu, 9 May 2024 16:36:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=9D=E8=AF=95=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E9=BB=91=E8=BE=B9=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Magpie.App/ScalingService.cpp | 5 +- src/Magpie.App/TouchHelper.cpp | 27 +++--- src/Magpie.App/TouchHelper.h | 2 +- src/Magpie.Core/ScalingOptions.h | 4 + src/Magpie.Core/ScalingWindow.cpp | 135 ++++++++++++++++++++++++++--- src/Magpie.Core/ScalingWindow.h | 6 +- src/Shared/CommonSharedConstants.h | 1 + src/TouchHelper/main.cpp | 28 +++--- 8 files changed, 167 insertions(+), 41 deletions(-) diff --git a/src/Magpie.App/ScalingService.cpp b/src/Magpie.App/ScalingService.cpp index 5df2dc69..60389a57 100644 --- a/src/Magpie.App/ScalingService.cpp +++ b/src/Magpie.App/ScalingService.cpp @@ -240,8 +240,6 @@ bool ScalingService::_StartScale(HWND hWnd, const Profile& profile) { } } } - - TouchHelper::TryLaunchTouchHelper(); options.graphicsCard = profile.graphicsCard; options.captureMethod = profile.captureMethod; @@ -306,6 +304,9 @@ bool ScalingService::_StartScale(HWND hWnd, const Profile& profile) { options.duplicateFrameDetectionMode = settings.DuplicateFrameDetectionMode(); options.IsStatisticsForDynamicDetectionEnabled(settings.IsStatisticsForDynamicDetectionEnabled()); + // 尝试启用触控支持 + options.IsTouchSupportEnabled(TouchHelper::TryLaunchTouchHelper()); + _isAutoScaling = profile.isAutoScale; _scalingRuntime->Start(hWnd, std::move(options)); _hwndCurSrc = hWnd; diff --git a/src/Magpie.App/TouchHelper.cpp b/src/Magpie.App/TouchHelper.cpp index 79fab03d..a1a3c40e 100644 --- a/src/Magpie.App/TouchHelper.cpp +++ b/src/Magpie.App/TouchHelper.cpp @@ -73,35 +73,38 @@ static bool CheckAndFixTouchHelper(std::wstring& path) noexcept { return true; } -void TouchHelper::TryLaunchTouchHelper() noexcept { +bool TouchHelper::TryLaunchTouchHelper() noexcept { std::wstring path = GetTouchHelperPath(); if (!Win32Utils::FileExists(path.c_str())) { // 未启用触控支持 - return; + return false; } wil::unique_mutex_nothrow hSingleInstanceMutex; bool alreadyExists = false; - if (hSingleInstanceMutex.try_create( + if (!hSingleInstanceMutex.try_create( CommonSharedConstants::TOUCH_HELPER_SINGLE_INSTANCE_MUTEX_NAME, CREATE_MUTEX_INITIAL_OWNER, MUTEX_ALL_ACCESS, nullptr, &alreadyExists - ) && !alreadyExists) { - hSingleInstanceMutex.ReleaseMutex(); + ) || alreadyExists) { + // TouchHelper.exe 正在运行 + return true; + } - // TouchHelper 未在运行则启动它 + hSingleInstanceMutex.ReleaseMutex(); - // 检查版本是否匹配并尝试修复 - if (!CheckAndFixTouchHelper(path)) { - // 修复失败 - return; - } + // TouchHelper.exe 未在运行则启动它 - Win32Utils::ShellOpen(path.c_str()); + // 检查版本是否匹配并尝试修复 + if (!CheckAndFixTouchHelper(path)) { + // 修复失败 + return false; } + + return Win32Utils::ShellOpen(path.c_str(), nullptr, false); } } diff --git a/src/Magpie.App/TouchHelper.h b/src/Magpie.App/TouchHelper.h index 139c9eff..3286cacd 100644 --- a/src/Magpie.App/TouchHelper.h +++ b/src/Magpie.App/TouchHelper.h @@ -5,7 +5,7 @@ namespace winrt::Magpie::App { struct TouchHelper { static bool IsTouchSupportEnabled() noexcept; static void IsTouchSupportEnabled(bool value) noexcept; - static void TryLaunchTouchHelper() noexcept; + static bool TryLaunchTouchHelper() noexcept; }; } diff --git a/src/Magpie.Core/ScalingOptions.h b/src/Magpie.Core/ScalingOptions.h index d5957e9b..6a12ed3d 100644 --- a/src/Magpie.Core/ScalingOptions.h +++ b/src/Magpie.Core/ScalingOptions.h @@ -44,6 +44,9 @@ struct ScalingFlags { static constexpr uint32_t DisableFontCache = 1 << 14; static constexpr uint32_t AllowScalingMaximized = 1 << 15; static constexpr uint32_t EnableStatisticsForDynamicDetection = 1 << 16; + // Magpie.Core 不负责启动 TouchHelper.exe,指定此标志会使 Magpie.Core 创建辅助窗口以拦截 + // 黑边上的触控输入 + static constexpr uint32_t IsTouchSupportEnabled = 1 << 17; }; enum class ScalingType { @@ -93,6 +96,7 @@ struct ScalingOptions { DEFINE_FLAG_ACCESSOR(IsDrawCursor, ScalingFlags::DrawCursor, flags) DEFINE_FLAG_ACCESSOR(IsDirectFlipDisabled, ScalingFlags::DisableDirectFlip, flags) DEFINE_FLAG_ACCESSOR(IsStatisticsForDynamicDetectionEnabled, ScalingFlags::EnableStatisticsForDynamicDetection, flags) + DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::IsTouchSupportEnabled, flags) Cropping cropping{}; uint32_t flags = ScalingFlags::AdjustCursorSpeed | ScalingFlags::DrawCursor; // ScalingFlags diff --git a/src/Magpie.Core/ScalingWindow.cpp b/src/Magpie.Core/ScalingWindow.cpp index 34c054fa..153de1fa 100644 --- a/src/Magpie.Core/ScalingWindow.cpp +++ b/src/Magpie.Core/ScalingWindow.cpp @@ -254,6 +254,10 @@ bool ScalingWindow::Create( } } + if (_options.IsTouchSupportEnabled()) { + _CreateTouchHoleWindows(hInstance); + } + // 在显示前设置窗口属性,其他程序应在缩放窗口显示后再检索窗口属性 _SetWindowProps(); @@ -298,6 +302,15 @@ bool ScalingWindow::Create( // 广播开始缩放 PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 1, (LPARAM)_hWnd); + for (const wil::unique_hwnd& hWnd : _hwndTouchHoles) { + if (!hWnd) { + continue; + } + + SetWindowPos(hWnd.get(), Handle(), 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + } + return true; } @@ -317,8 +330,9 @@ void ScalingWindow::Render() noexcept { if (_renderer->Render()) { // 为了避免用户看到 DDF 窗口,在渲染第一帧后显示 if (_hwndDDF && !_isDDFWindowShown) { - ShowWindow(_hwndDDF, SW_NORMAL); - SetWindowPos(_hwndDDF, Handle(), 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW); + ShowWindow(_hwndDDF.get(), SW_SHOWNOACTIVATE); + SetWindowPos(_hwndDDF.get(), Handle(), 0, 0, 0, 0, + SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOACTIVATE | SWP_NOCOPYBITS); _isDDFWindowShown = true; } } @@ -403,10 +417,11 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n { _exclModeMutex.reset(); - if (_hwndDDF) { - DestroyWindow(_hwndDDF); - _hwndDDF = NULL; - _isDDFWindowShown = false; + _hwndDDF.reset(); + _isDDFWindowShown = false; + + for (wil::unique_hwnd& hWnd : _hwndTouchHoles) { + hWnd.reset(); } _cursorManager.reset(); @@ -539,7 +554,7 @@ bool ScalingWindow::_DisableDirectFlip(HINSTANCE hInstance) noexcept { return Utils::Ignore(); }(hInstance); - _hwndDDF = CreateWindowEx( + _hwndDDF.reset(CreateWindowEx( WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TRANSPARENT, CommonSharedConstants::DDF_WINDOW_CLASS_NAME, NULL, @@ -552,22 +567,22 @@ bool ScalingWindow::_DisableDirectFlip(HINSTANCE hInstance) noexcept { NULL, hInstance, NULL - ); + )); - if (!_hwndDDF) { + if (!_hwndDDF.get()) { Logger::Get().Win32Error("创建 DDF 窗口失败"); return false; } // 设置窗口不透明 - if (!SetLayeredWindowAttributes(_hwndDDF, 0, 255, LWA_ALPHA)) { + if (!SetLayeredWindowAttributes(_hwndDDF.get(), 0, 255, LWA_ALPHA)) { Logger::Get().Win32Error("SetLayeredWindowAttributes 失败"); } if (_renderer->FrameSource().IsScreenCapture()) { if (Win32Utils::GetOSVersion().Is20H1OrNewer()) { // 使 DDF 窗口无法被捕获到 - if (!SetWindowDisplayAffinity(_hwndDDF, WDA_EXCLUDEFROMCAPTURE)) { + if (!SetWindowDisplayAffinity(_hwndDDF.get(), WDA_EXCLUDEFROMCAPTURE)) { Logger::Get().Win32Error("SetWindowDisplayAffinity 失败"); } } @@ -593,4 +608,102 @@ void ScalingWindow::_SetWindowProps() const noexcept { SetProp(_hWnd, L"Magpie.DestBottom", (HANDLE)(INT_PTR)destRect.bottom); } +// 在源窗口四周创建辅助窗口拦截黑边上的触控点击。直接将 srcRect 映射到 destRect +// 是天真的想法: +// +// 1. 同样需要拦截黑边的机制,可以创建一个全屏的背景窗口来解决。 +// 2. 更严重的问题是无法解决源窗口和黑边的重叠部分。作为黑边,本应拦截用户点击,但 +// 这也拦截了对源窗口的操作;若是不拦截会导致在黑边上可以操作源窗口。 +// +// 我们的方案是:将源窗口和周围映射到整个缩放窗口,并在源窗口四周创建背景窗口拦截 +// 对黑边的点击。 +void ScalingWindow::_CreateTouchHoleWindows(HINSTANCE hInstance) noexcept { + // 将黑边映射到源窗口 + const RECT& srcRect = _renderer->SrcRect(); + const RECT& destRect = _renderer->DestRect(); + + const double scaleX = double(destRect.right - destRect.left) / (srcRect.right - srcRect.left); + const double scaleY = double(destRect.bottom - destRect.top) / (srcRect.bottom - srcRect.top); + + RECT srcTouchRect = srcRect; + + if (destRect.left > _wndRect.left) { + srcTouchRect.left -= lround((destRect.left - _wndRect.left) / scaleX); + } + if (destRect.top > _wndRect.top) { + srcTouchRect.top -= lround((destRect.top - _wndRect.top) / scaleX); + } + if (destRect.right < _wndRect.right) { + srcTouchRect.right += lround((_wndRect.right - destRect.right) / scaleY); + } + if (destRect.bottom < _wndRect.bottom) { + srcTouchRect.bottom += lround((_wndRect.bottom - destRect.bottom) / scaleY); + } + + static Utils::Ignore _ = [](HINSTANCE hInstance) { + WNDCLASSEXW wcex{ + .cbSize = sizeof(wcex), + .lpfnWndProc = DefWindowProc, + .hInstance = hInstance, + .hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH), + .lpszClassName = CommonSharedConstants::TOUCH_HELPER_HOLE_WINDOW_CLASS_NAME + }; + RegisterClassEx(&wcex); + + return Utils::Ignore(); + }(hInstance); + + const auto createHoleWindow = [&](uint32_t idx, LONG left, LONG top, LONG right, LONG bottom) noexcept { + _hwndTouchHoles[idx].reset(CreateWindowEx( + 0/*WS_EX_NOREDIRECTIONBITMAP | WS_EX_NOACTIVATE*/, + CommonSharedConstants::TOUCH_HELPER_HOLE_WINDOW_CLASS_NAME, + nullptr, + WS_POPUP, + left, + top, + right - left, + bottom - top, + NULL, + NULL, + wil::GetModuleInstanceHandle(), + 0 + )); + }; + + // srcTouchRect + // ┌───┬───────────┬───┐ + // │ │ 1 │ │ + // │ ├───────────┤ │ + // │ │ │ │ + // │ 0 │ srcRect │ 2 │ + // │ │ │ │ + // │ ├───────────┤ │ + // │ │ 3 │ │ + // └───┴───────────┴───┘ + + if (srcRect.left > srcTouchRect.left) { + createHoleWindow(0, srcTouchRect.left, srcTouchRect.top, srcRect.left, srcTouchRect.bottom); + } + if (srcRect.top > srcTouchRect.top) { + createHoleWindow(1, srcRect.left, srcTouchRect.top, srcRect.right, srcRect.top); + } + if (srcRect.right < srcTouchRect.right) { + createHoleWindow(2, srcRect.right, srcTouchRect.top, srcTouchRect.right, srcTouchRect.bottom); + } + if (srcRect.bottom < srcTouchRect.bottom) { + createHoleWindow(3, srcRect.left, srcRect.bottom, srcRect.right, srcTouchRect.bottom); + } + + // 供 TouchHelper.exe 使用 + SetProp(_hWnd, L"Magpie.SrcTouchLeft", (HANDLE)(INT_PTR)srcTouchRect.left); + SetProp(_hWnd, L"Magpie.SrcTouchTop", (HANDLE)(INT_PTR)srcTouchRect.top); + SetProp(_hWnd, L"Magpie.SrcTouchRight", (HANDLE)(INT_PTR)srcTouchRect.right); + SetProp(_hWnd, L"Magpie.SrcTouchBottom", (HANDLE)(INT_PTR)srcTouchRect.bottom); + + SetProp(_hWnd, L"Magpie.DestTouchLeft", (HANDLE)(INT_PTR)_wndRect.left); + SetProp(_hWnd, L"Magpie.DestTouchTop", (HANDLE)(INT_PTR)_wndRect.top); + SetProp(_hWnd, L"Magpie.DestTouchRight", (HANDLE)(INT_PTR)_wndRect.right); + SetProp(_hWnd, L"Magpie.DestTouchBottom", (HANDLE)(INT_PTR)_wndRect.bottom); +} + } diff --git a/src/Magpie.Core/ScalingWindow.h b/src/Magpie.Core/ScalingWindow.h index de18c61d..b1b4783f 100644 --- a/src/Magpie.Core/ScalingWindow.h +++ b/src/Magpie.Core/ScalingWindow.h @@ -73,6 +73,8 @@ class ScalingWindow : public WindowBase { void _SetWindowProps() const noexcept; + void _CreateTouchHoleWindows(HINSTANCE hInstance) noexcept; + winrt::DispatcherQueue _dispatcher{ nullptr }; RECT _wndRect{}; @@ -84,9 +86,11 @@ class ScalingWindow : public WindowBase { HWND _hwndSrc = NULL; RECT _srcWndRect{}; - HWND _hwndDDF = NULL; + wil::unique_hwnd _hwndDDF; wil::unique_mutex_nothrow _exclModeMutex; + std::array _hwndTouchHoles{}; + bool _isSrcRepositioning = false; bool _isDDFWindowShown = false; }; diff --git a/src/Shared/CommonSharedConstants.h b/src/Shared/CommonSharedConstants.h index addebbcb..1d2d01bb 100644 --- a/src/Shared/CommonSharedConstants.h +++ b/src/Shared/CommonSharedConstants.h @@ -11,6 +11,7 @@ struct CommonSharedConstants { static constexpr const wchar_t* SCALING_WINDOW_CLASS_NAME = L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22"; static constexpr const wchar_t* DDF_WINDOW_CLASS_NAME = L"Window_Magpie_C322D752-C866-4630-91F5-32CB242A8930"; static constexpr const wchar_t* TOUCH_HELPER_WINDOW_CLASS_NAME = L"Magpie_TouchHelper"; + static constexpr const wchar_t* TOUCH_HELPER_HOLE_WINDOW_CLASS_NAME = L"Magpie_TouchHelperHole"; static constexpr const wchar_t* TOUCH_HELPER_EXE_NAME = L"TouchHelper.exe"; // TouchHelper 有重要更改则提高版本号 diff --git a/src/TouchHelper/main.cpp b/src/TouchHelper/main.cpp index 1a1a073c..8887eb13 100644 --- a/src/TouchHelper/main.cpp +++ b/src/TouchHelper/main.cpp @@ -7,13 +7,13 @@ static UINT WM_MAGPIE_SCALINGCHANGED; // 0: Magpie 通知 TouchHelper 退出 // 1: TouchHelper 向缩放窗口报告结果,lParam 为 0 表示成功,否则为错误代码 static UINT WM_MAGPIE_TOUCHHELPER; -static HWND curHwndScaling = NULL; +static HWND hwndCurScaling = NULL; static void UpdateInputTransform(HWND hwndScaling) noexcept { - if (curHwndScaling == hwndScaling) { + if (hwndCurScaling == hwndScaling) { return; } - curHwndScaling = hwndScaling; + hwndCurScaling = hwndScaling; if (hwndScaling == NULL) { RECT ununsed{}; @@ -21,22 +21,22 @@ static void UpdateInputTransform(HWND hwndScaling) noexcept { return; } - RECT srcRect{ - .left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcLeft"), - .top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTop"), - .right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcRight"), - .bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcBottom") + RECT srcTouchRect{ + .left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTouchLeft"), + .top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTouchTop"), + .right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTouchRight"), + .bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTouchBottom") }; - RECT destRect{ - .left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestLeft"), - .top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTop"), - .right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestRight"), - .bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestBottom") + RECT destTouchRect{ + .left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTouchLeft"), + .top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTouchTop"), + .right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTouchRight"), + .bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTouchBottom") }; DWORD errorCode = 0; - if (!MagSetInputTransform(TRUE, &srcRect, &destRect)) { + if (!MagSetInputTransform(TRUE, &srcTouchRect, &destTouchRect)) { errorCode = GetLastError(); }