Skip to content

Commit

Permalink
feat: 尝试解决黑边问题
Browse files Browse the repository at this point in the history
  • Loading branch information
Blinue committed May 9, 2024
1 parent ba2544b commit bc00a16
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 41 deletions.
5 changes: 3 additions & 2 deletions src/Magpie.App/ScalingService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,6 @@ bool ScalingService::_StartScale(HWND hWnd, const Profile& profile) {
}
}
}

TouchHelper::TryLaunchTouchHelper();

options.graphicsCard = profile.graphicsCard;
options.captureMethod = profile.captureMethod;
Expand Down Expand Up @@ -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;
Expand Down
27 changes: 15 additions & 12 deletions src/Magpie.App/TouchHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
2 changes: 1 addition & 1 deletion src/Magpie.App/TouchHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

}
4 changes: 4 additions & 0 deletions src/Magpie.Core/ScalingOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
135 changes: 124 additions & 11 deletions src/Magpie.Core/ScalingWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ bool ScalingWindow::Create(
}
}

if (_options.IsTouchSupportEnabled()) {
_CreateTouchHoleWindows(hInstance);
}

// 在显示前设置窗口属性,其他程序应在缩放窗口显示后再检索窗口属性
_SetWindowProps();

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand All @@ -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 失败");
}
}
Expand All @@ -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);
}

}
6 changes: 5 additions & 1 deletion src/Magpie.Core/ScalingWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class ScalingWindow : public WindowBase<ScalingWindow> {

void _SetWindowProps() const noexcept;

void _CreateTouchHoleWindows(HINSTANCE hInstance) noexcept;

winrt::DispatcherQueue _dispatcher{ nullptr };

RECT _wndRect{};
Expand All @@ -84,9 +86,11 @@ class ScalingWindow : public WindowBase<ScalingWindow> {
HWND _hwndSrc = NULL;
RECT _srcWndRect{};

HWND _hwndDDF = NULL;
wil::unique_hwnd _hwndDDF;
wil::unique_mutex_nothrow _exclModeMutex;

std::array<wil::unique_hwnd, 4> _hwndTouchHoles{};

bool _isSrcRepositioning = false;
bool _isDDFWindowShown = false;
};
Expand Down
1 change: 1 addition & 0 deletions src/Shared/CommonSharedConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 有重要更改则提高版本号
Expand Down
28 changes: 14 additions & 14 deletions src/TouchHelper/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,36 @@ 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{};
MagSetInputTransform(FALSE, &ununsed, &ununsed);
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();
}

Expand Down

0 comments on commit bc00a16

Please sign in to comment.