Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QCursor::setPos sets wrong X coordinates



  • Hi,

    I am trying to update an existing application to support DPI scaling and I have issues with code that update the cursor position using QCursor::setPos.

    My configuration:

    • Linux
    • Two 4K screens at 200% scaling.
    • My Primary Display is the right one, secondary the left one.
    • Qt's Primary display is also the right one (application opens on the right one by default).
    • My screens are reversed in my OS settings (screen 1 is the right one, screen 2 is the left one as recognized by my GPU)

    The code that moves the cursor works when the application is on the right screen (primary screen) but does not work when the application is on the secondary screen.

    It boils down to the following code:

    const auto offset = ...; // Some code that computes an offset
    const auto currentPos = cursor->pos();
    const auto newPos = currentPos + offset;
    cursor->setPos(newPos);
    

    When run on my primary screen, I get:

    • currentPos = QPoint(4419, 111)
    • newPos = QPoint(5750, 111)
    • After calling setPos, cursor->pos() = QPoint(5750, 111) (equal to what was expected)

    When run on my secondary screen, I get:

    • currentPos = QPoint(582, 70)
    • newPos = QPoint(1913, 70)
    • After calling setPos, cursor->pos() = QPoint(0, 70)

    Why is my Y coordinate kept but the X is reset to 0?
    If I run the same code on my screens in 1080p 100% scaling it works correctly so it seems something in QCursor::setPos does not accept my X coordinates when DPI scaling is active. What is weird is that 1913 would be valid coordinates whether or not DPI scaling is enabled since it falls into the screen anyways.

    Any idea what might go wrong here?
    Thanks

    edit:
    My application is in QML but the part that handles cursor movement is in C++ and a QCursor object.

    And here's the list of configurations on which I could repeat the issue. Issue always happen on the secondary display. All my tests have been done on computers with a right primary display and left secondary display.

    Arch Linux

    • DE: Gnome 41
    • Server: Xorg
    • Qt version: unmodified 5.15.2
    • Primary display: 4K @200% scaling
    • Secondary display: 4K @200% scaling

    Ubuntu 21.04

    • DE: Gnome 3.38.5
    • Server: Xorg
    • Qt version: unmodified 5.15.2
    • Primary display: 1440p @200% scaling
    • Secondary display: 1440p @200% scaling

    Windows 10

    • Qt version: unmodified 5.15.2
    • Primary display: 1920×1080 @150% scaling (didn't try @100% scaling, in hindsight I should have)
    • Secondary display: 4K @200% scaling

  • Lifetime Qt Champion

    Hi,

    Which distribution are you using ?
    Which desktop environment ?
    Which graphic server ? Xorg or Wayland ?
    Which version of Qt ?



  • Distro: Arch Linux
    DE: Gnome 41
    Server: Xorg
    Qt version: Unmodified 5.15.2

    I'll be able to check on an Ubuntu 21.04 with Wayland tomorrow.



  • Tried on two other machine:s

    First one:
    Distro: Ubuntu 21.04
    DE: Gnome 3.38.5
    Server: Xorg (thought that was Wayland yesterday, I was wrong)
    Qt version: Unmodified 5.15.2

    On this machines the primary display is still the right screen but screens are in the correct order in the OS's settings so this has nothing to do with this.

    Dual screen 1440p:

    • Tested without DPI scaling: No issue
    • Tested with 200% scaling: same issue that on my other computer when I move the window to my left (secondary) screen.

    Second one:
    OS: Windows 10
    Qt version: Unmodified 5.15.2

    On this machine the primary display is 1920×1080 and secondary display is 4K with 200% scaling. The issue happens on this configuration too on the secondary display.

    I've updated the top comment with all the configs I've tested so far and a disclaimer for the fact that even though the QCursor is handled in C++ my entire app is in QML


  • Lifetime Qt Champion

    Might be a silly question but did you already went through the High DPI chapter in Qt's documentation ?



  • I did, I am currently using the Qt::AA_EnableHighDpiScaling option only, without any environment variable modifier. And all my application is rendering correctly on both 1920×1080 and 4K screens. Still have issues with non-integer DPI scaling values but the issue here happens on 200% scaling so I don't think it's related (I didn't knew about the Passthrough option added in Qt 5.14 for DPI scaling values, I'll try that later).

    Something that might be important: the item over which I notice the issue is a QQuickFrameBufferObject, but I make sure to always work in pixel-independent coordinates when using QCursor (I am sure that the coordinates I give to QCursor::setPos are correct, as you can see in the values I logged in my first message)



  • Here a small program that shows the issue. I couldn't find a way to upload files so I copy pasted the content below.

    main.cpp:

    #include "myitem.hpp"
    
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QString>
    #include <QSurfaceFormat>
    #include <QUrl>
    #include <QtQml>
    
    int main(int argc, char** argv) {
    
    	{
    		QGuiApplication::setAttribute(Qt::ApplicationAttribute::AA_ShareOpenGLContexts);
    		QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    		QGuiApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
    
    		// Surface
    		// Force OpenGL 3.3 core profile
    		QSurfaceFormat format;
    		format.setVersion(3, 3);
    		format.setProfile(QSurfaceFormat::CoreProfile);
    		QSurfaceFormat::setDefaultFormat(format);
    	}
    
    	QGuiApplication app(argc, argv);
    
    	qmlRegisterType<MyItem>("MyLib", 1, 0, "MyItem");
    
    	QQmlApplicationEngine engine;
    
    	engine.load(QUrl(QString("qrc:/qml/main.qml")));
    
    	return app.exec();
    }
    
    

    myitem.hpp

    #pragma once
    
    #include <QQuickItem>
    
    #include <memory>
    
    class QMouseEvent;
    
    class MyItem : public QQuickItem {
    	Q_OBJECT
    
      public:
    	MyItem(QQuickItem* parent = nullptr);
    	~MyItem() override;
    
      protected:
    	void mousePressEvent(QMouseEvent* event) override;
    	void mouseReleaseEvent(QMouseEvent* event) override;
    	void mouseMoveEvent(QMouseEvent* event) override;
    
      private:
    	void handleMouseWarping(QMouseEvent& event);
    
      private:
    	struct Private;
    	std::unique_ptr<Private> d;
    };
    

    myitem.cpp

    #include "myitem.hpp"
    
    #include <QCursor>
    #include <QMouseEvent>
    
    #include <iostream>
    
    struct MyItem::Private {
    	bool mouseWarpingEnabled{ false };
    	unsigned int mouseWarpingMargin{ 5u };
    	QCursor cursor;
    };
    
    MyItem::MyItem(QQuickItem* parent) : QQuickItem{ parent }, d{ new Private } {
    	setAcceptedMouseButtons(Qt::LeftButton);
    }
    
    MyItem::~MyItem() = default;
    
    void MyItem::mousePressEvent(QMouseEvent* event) {
    	d->mouseWarpingEnabled = true;
    }
    
    void MyItem::mouseReleaseEvent(QMouseEvent* event) {
    	d->mouseWarpingEnabled = false;
    }
    
    void MyItem::mouseMoveEvent(QMouseEvent* event) {
    	handleMouseWarping(*event);
    }
    
    // ==== PRIVATE IMPL
    
    void MyItem::handleMouseWarping(QMouseEvent& event) {
    	// Only manipulate QCursor coordinates to prevent having a desync between cursor and event information
    
    	if(!d->mouseWarpingEnabled) {
    		event.ignore();
    		return;
    	}
    
    	const auto globalPos = d->cursor.pos();
    	const auto topLeftRectPos = mapToGlobal({ 0, 0 });
    	const auto localPos = globalPos - topLeftRectPos;
    
    	QPoint offset{ 0, 0 };
    
    	if(localPos.x() < d->mouseWarpingMargin) {
    		offset.setX(width() - 2 * d->mouseWarpingMargin - 1);
    	}
    	else if(localPos.x() > width() - d->mouseWarpingMargin) {
    		offset.setX(-(width() - 2 * d->mouseWarpingMargin - 1));
    	}
    
    	if(localPos.y() < d->mouseWarpingMargin) {
    		offset.setY(height() - 2 * d->mouseWarpingMargin - 1);
    	}
    	else if(localPos.y() > height() - d->mouseWarpingMargin) {
    		offset.setY(-(height() - 2 * d->mouseWarpingMargin - 1));
    	}
    
    	if(offset.x() != 0 || offset.y() != 0) {
    		const auto newGlobalPos = globalPos + offset;
    		d->cursor.setPos(newGlobalPos);
    
    		const auto actualNewGlobalPos = d->cursor.pos();
    		std::cout << "Expected new coordinates: " << newGlobalPos.x() << ", " << newGlobalPos.y()
    		          << ". Actual new coordinates: " << actualNewGlobalPos.x() << ", " << actualNewGlobalPos.y() << std::endl;
    	}
    }
    
    

    main.qml

    import QtQuick 2.15
    import QtQuick.Controls 2.15
    
    import MyLib 1.0
    
    ApplicationWindow {
        id : root
        title : 'Mouse warping issue in 4K'
        visible : true
    
        width : 800
        height : 400
    
    	color: Qt.hsla(0, 0, 0.13)
    
    	MyItem {
    		anchors.fill: parent
    
    		Text {
    			text: 'Press left-click down and move over to edge of window to warp to the other side'
    			anchors.centerIn: parent
    			color: Qt.hsla(0, 0, 0.89)
    		}
    	}
    }
    

    qml.qrc

    <RCC>
        <qresource prefix="/qml">
            <file>main.qml</file>
        </qresource>
    </RCC>
    
    

Log in to reply