From e440c6233920c5a98e28a3132263e81f79d5f438 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 17 Oct 2026 00:16:56 +0200 Subject: [PATCH] Use native Windows monitor handle for cursor overlay --- tools/chatgui/CMakeLists.txt | 10 ++++-- .../chatgui/src/stream_screen_cursor_win.cpp | 31 +++++++++++++------ tools/chatgui/tests/test_stream_publish.cpp | 14 ++++++--- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 0c1abdac..b805b5df 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -219,9 +219,13 @@ if(GUI_CAPTURE_ENABLED AND UNIX AND NOT APPLE AND QT_FEATURE_xcb) target_link_libraries(chatgui_stream_publish PRIVATE X11::X11 X11::Xfixes) endif() if(GUI_CAPTURE_ENABLED AND WIN32) - target_sources(chatgui_stream_publish PRIVATE src/stream_screen_cursor_win.cpp) - target_compile_definitions(chatgui_stream_publish PUBLIC UTUN_STREAM_WIN_CURSOR) - target_link_libraries(chatgui_stream_publish PRIVATE user32 gdi32) + if(Qt6_VERSION VERSION_GREATER_EQUAL 6.7) + target_sources(chatgui_stream_publish PRIVATE src/stream_screen_cursor_win.cpp) + target_compile_definitions(chatgui_stream_publish PUBLIC UTUN_STREAM_WIN_CURSOR) + target_link_libraries(chatgui_stream_publish PRIVATE user32 gdi32) + else() + message(STATUS "Windows cursor overlay requires Qt 6.7+ native monitor handles") + endif() endif() add_library(chatgui_notifications STATIC src/message_notification.cpp src/user_activity_monitor.cpp) diff --git a/tools/chatgui/src/stream_screen_cursor_win.cpp b/tools/chatgui/src/stream_screen_cursor_win.cpp index 155918bb..e9f8cfa6 100644 --- a/tools/chatgui/src/stream_screen_cursor_win.cpp +++ b/tools/chatgui/src/stream_screen_cursor_win.cpp @@ -2,6 +2,7 @@ #include "debug_config.h" #include #include +#include #include #include @@ -27,14 +28,26 @@ bool StreamScreenCursor::draw(QImage& frame) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor: GetCursorInfo failed error=%lu", GetLastError()); return false; } if (!(cursor.flags & CURSOR_SHOWING)) return true; - DEVMODEW mode{}; mode.dmSize = sizeof(mode); - const auto device = m_screen->name().toStdWString(); - if (!EnumDisplaySettingsExW(device.c_str(), ENUM_CURRENT_SETTINGS, &mode, 0)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor: display mode unavailable screen=%s error=%lu", + auto* native = m_screen->nativeInterface(); + if (!native) { + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor: native monitor unavailable screen=%s", qPrintable(m_screen->name())); + return false; + } + MONITORINFOEXW monitor{}; monitor.cbSize = sizeof(monitor); + if (!GetMonitorInfoW(native->handle(), reinterpret_cast(&monitor))) { + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor: GetMonitorInfo failed screen=%s error=%lu", qPrintable(m_screen->name()), GetLastError()); return false; } - // Qt geometry имеет логический размер; WinAPI возвращает физическое начало, в том числе отрицательное. - QPoint position(cursor.ptScreenPos.x - mode.dmPosition.x, cursor.ptScreenPos.y - mode.dmPosition.y); + const QSize monitorSize(monitor.rcMonitor.right - monitor.rcMonitor.left, + monitor.rcMonitor.bottom - monitor.rcMonitor.top); + if (frame.size() != monitorSize) { + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor: captured frame does not match monitor screen=%s frame=%dx%d monitor=%dx%d", + qPrintable(m_screen->name()), frame.width(), frame.height(), monitorSize.width(), monitorSize.height()); + return false; + } + // RECT использует физические координаты общего виртуального desktop, включая отрицательные origins. + QPoint origin(monitor.rcMonitor.left, monitor.rcMonitor.top); + QPoint position(cursor.ptScreenPos.x - origin.x(), cursor.ptScreenPos.y - origin.y()); ICONINFO icon{}; if (!GetIconInfo(cursor.hCursor, &icon)) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor: GetIconInfo failed error=%lu", GetLastError()); return false; @@ -91,9 +104,9 @@ bool StreamScreenCursor::draw(QImage& frame) { if (!painter.isActive()) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop cursor painter initialization failed"); return false; } painter.drawImage(area.topLeft(), composed); if (!m_reported) { - DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop cursor drawn screen=%s origin=%ld,%ld position=%d,%d hotspot=%lu,%lu sprite=%dx%d frame=%dx%d", - qPrintable(m_screen->name()), mode.dmPosition.x, mode.dmPosition.y, position.x(), position.y(), icon.xHotspot, icon.yHotspot, - size.width(), size.height(), frame.width(), frame.height()); + DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop cursor drawn screen=%s device=%ls origin=%d,%d position=%d,%d hotspot=%lu,%lu sprite=%dx%d frame=%dx%d", + qPrintable(m_screen->name()), monitor.szDevice, origin.x(), origin.y(), position.x(), position.y(), icon.xHotspot, + icon.yHotspot, size.width(), size.height(), frame.width(), frame.height()); m_reported = true; } return true; diff --git a/tools/chatgui/tests/test_stream_publish.cpp b/tools/chatgui/tests/test_stream_publish.cpp index e59d6546..153b817c 100644 --- a/tools/chatgui/tests/test_stream_publish.cpp +++ b/tools/chatgui/tests/test_stream_publish.cpp @@ -57,6 +57,7 @@ extern "C" { #ifdef UTUN_STREAM_WIN_CURSOR #include "../src/stream_screen_cursor.h" #include +#include #include #endif @@ -422,8 +423,10 @@ private slots: POINT saved{}; QVERIFY(GetCursorPos(&saved)); auto restorePosition = qScopeGuard([&] { SetCursorPos(saved.x, saved.y); }); for (auto* screen : QGuiApplication::screens()) { - DEVMODEW mode{}; mode.dmSize = sizeof(mode); - QVERIFY(EnumDisplaySettingsExW(screen->name().toStdWString().c_str(), ENUM_CURRENT_SETTINGS, &mode, 0)); + auto* native = screen->nativeInterface(); QVERIFY(native); + MONITORINFO monitor{}; monitor.cbSize = sizeof(monitor); + QVERIFY(GetMonitorInfoW(native->handle(), &monitor)); + QPoint origin(monitor.rcMonitor.left, monitor.rcMonitor.top); QWindow target(screen); target.setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); target.setGeometry(QRect(screen->geometry().topLeft(), QSize(200, 160))); target.show(); QVERIFY(QTest::qWaitForWindowExposed(&target)); @@ -436,11 +439,12 @@ private slots: StreamScreenCursor overlay(screen); const QColor background(0x12, 0x34, 0x56), inverted(0xed, 0xcb, 0xa9); for (QPoint position : {QPoint(60, 70), QPoint(0, 0)}) { - QVERIFY(SetCursorPos(mode.dmPosition.x + position.x(), mode.dmPosition.y + position.y())); + QVERIFY(SetCursorPos(origin.x() + position.x(), origin.y() + position.y())); SetCursor(cursor); CURSORINFO info{}; info.cbSize = sizeof(info); QVERIFY(GetCursorInfo(&info)); QVERIFY(info.flags & CURSOR_SHOWING); QVERIFY(info.hCursor == cursor); - QImage frame(mode.dmPelsWidth, mode.dmPelsHeight, QImage::Format_RGB32); + QImage frame(monitor.rcMonitor.right - monitor.rcMonitor.left, + monitor.rcMonitor.bottom - monitor.rcMonitor.top, QImage::Format_RGB32); frame.setDevicePixelRatio(screen->devicePixelRatio()); frame.fill(background); QImage blank = frame; QVERIFY(overlay.draw(frame)); @@ -451,7 +455,7 @@ private slots: SetCursor(nullptr); frame = blank; QVERIFY(overlay.draw(frame)); QCOMPARE(frame, blank); } - QVERIFY(SetCursorPos(mode.dmPosition.x + 60, mode.dmPosition.y + 70)); SetCursor(cursor); + QVERIFY(SetCursorPos(origin.x() + 60, origin.y() + 70)); SetCursor(cursor); for (auto* other : QGuiApplication::screens()) { if (other == screen) continue; StreamScreenCursor otherOverlay(other);