Use native Windows monitor handle for cursor overlay

This commit is contained in:
evgeny committed 2026-10-17 00:16:56 +02:00
1 parent 4a6f95cebd
commit e440c62339
3 files changed
+38 -17

No files matched your search

+7 -3
View File
@@ -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)
+22 -9
View File
@@ -2,6 +2,7 @@
#include "debug_config.h"
#include <QPainter>
#include <QScopeGuard>
#include <QtGui/qscreen_platform.h>
#include <qt_windows.h>
#include <cstring>
@@ -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<QNativeInterface::QWindowsScreen>();
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<MONITORINFO*>(&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;
+9 -5
View File
@@ -57,6 +57,7 @@ extern "C" {
#ifdef UTUN_STREAM_WIN_CURSOR
#include "../src/stream_screen_cursor.h"
#include <QWindow>
#include <QtGui/qscreen_platform.h>
#include <qt_windows.h>
#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<QNativeInterface::QWindowsScreen>(); 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);