Skip to content

Commit

Permalink
Client: Decrease buffer_scale for small cursor themes
Browse files Browse the repository at this point in the history
Not all setups or themes have cursors with a matching DPI. In such cases,
wl_cursor_load_theme will then return a theme that is the closest resolution it
can get.

With the previous implementation, cursors themes without a high dpi version
would become become really tiny on high DPI displays.

This patch prevents it by setting a lower wl_surface.scale for those themes.

This also implements proper tracking of the cursor surface's entered outputs
(i.e. if the entered surface is destroyed, the scale is reset, and similarly
the following sequence of events should also be handled:

    wl_surface.enter(wl_output@1)
    wl_surface.enter(wl_output@2)
    wl_surface.leave(wl_output@2)

In the old implementation, we would be stuck with the scale from wl_output@2,
but now we now should correctly get the scale of wl_output@1.

[ChangeLog][QPA plugin] Cursors on high DPI screens are now scaled up if the
cursor theme does not have an appropriate high resolution version.

Change-Id: Ic87d00e35612b5afdf8c2e3a4463fcfef1f1f09d
Reviewed-by: Giulio Camuffo <giulio.camuffo@kdab.com>
  • Loading branch information
Johan Klokkhammer Helsing committed Feb 27, 2019
1 parent 2f0e6d7 commit 021bd4d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 11 deletions.
52 changes: 44 additions & 8 deletions src/client/qwaylandinputdevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,23 @@ QWaylandInputDevice::Pointer::~Pointer()

#if QT_CONFIG(cursor)

class CursorSurface : public QtWayland::wl_surface {
class CursorSurface : public QObject, public QtWayland::wl_surface
{
public:
explicit CursorSurface(QWaylandInputDevice::Pointer *pointer, QWaylandDisplay *display)
: m_pointer(pointer)
{
init(display->createSurface(this));
//TODO: When we upgrade to libwayland 1.10, use wl_surface_get_version instead.
m_version = display->compositorVersion();
connect(qApp, &QGuiApplication::screenRemoved, this, [this](QScreen *screen) {
int oldScale = outputScale();
if (!m_screens.removeOne(static_cast<QWaylandScreen *>(screen->handle())))
return;

if (outputScale() != oldScale)
m_pointer->updateCursor();
});
}

void hide()
Expand Down Expand Up @@ -225,26 +234,46 @@ class CursorSurface : public QtWayland::wl_surface {
commit();
}

int outputScale() const { return m_outputScale; }
int outputScale() const
{
int scale = 0;
for (auto *screen : m_screens)
scale = qMax(scale, screen->scale());
return scale;
}

protected:
void surface_enter(struct ::wl_output *output) override
{
//TODO: Can be improved to keep track of all entered screens
int scale = QWaylandScreen::fromWlOutput(output)->scale();
if (scale == m_outputScale)
int oldScale = outputScale();
auto *screen = QWaylandScreen::fromWlOutput(output);
if (m_screens.contains(screen))
return;

m_screens.append(screen);

if (outputScale() != oldScale)
m_pointer->updateCursor();
}

void surface_leave(struct ::wl_output *output) override
{
int oldScale = outputScale();
auto *screen = QWaylandScreen::fromWlOutput(output);

if (!m_screens.removeOne(screen))
return;

m_outputScale = scale;
m_pointer->updateCursor();
if (outputScale() != oldScale)
m_pointer->updateCursor();
}

private:
QWaylandInputDevice::Pointer *m_pointer = nullptr;
uint m_version = 0;
uint m_setSerial = 0;
QPoint m_hotspot;
int m_outputScale = 0;
QVector<QWaylandScreen *> m_screens;
};

QString QWaylandInputDevice::Pointer::cursorThemeName() const
Expand Down Expand Up @@ -280,6 +309,13 @@ void QWaylandInputDevice::Pointer::updateCursorTheme()
int pixelSize = cursorSize() * scale;
auto *display = seat()->mQDisplay;
mCursor.theme = display->loadCursorTheme(cursorThemeName(), pixelSize);
if (auto *arrow = mCursor.theme->cursorImage(Qt::ArrowCursor)) {
int arrowPixelSize = qMax(arrow->width, arrow->height); // Not all cursor themes are square
while (scale > 1 && arrowPixelSize / scale < cursorSize())
--scale;
} else {
qWarning(lcQpaWayland) << "Cursor theme does not support the arrow cursor";
}
mCursor.themeBufferScale = scale;
}

Expand Down
62 changes: 59 additions & 3 deletions tests/auto/client/seatv4/tst_seatv4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ private slots:
void simpleAxis();
void invalidPointerEvents();
void scaledCursor();
void unscaledFallbackCursor();
void bitmapCursor();
void hidpiBitmapCursor();
void hidpiBitmapCursorNonInt();
Expand Down Expand Up @@ -290,7 +291,7 @@ void tst_seatv4::invalidPointerEvents()

static bool supportsCursorSize(uint size, wl_shm *shm)
{
auto *theme = wl_cursor_theme_load(nullptr, size, shm);
auto *theme = wl_cursor_theme_load(qgetenv("XCURSOR_THEME"), size, shm);
if (!theme)
return false;

Expand All @@ -313,10 +314,16 @@ static bool supportsCursorSizes(const QVector<uint> &sizes)
});
}

static uint defaultCursorSize() {
const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
return xCursorSize > 0 ? uint(xCursorSize) : 32;
}

void tst_seatv4::scaledCursor()
{
if (!supportsCursorSizes({32, 64}))
QSKIP("Cursor themes with sizes 32 and 64 not found.");
const uint defaultSize = defaultCursorSize();
if (!supportsCursorSizes({defaultSize, defaultSize * 2}))
QSKIP("Cursor themes with default size and 2x default size not found.");

// Add a highdpi output
exec([&] {
Expand Down Expand Up @@ -351,6 +358,55 @@ void tst_seatv4::scaledCursor()
exec([&] { remove(output(1)); });
}

void tst_seatv4::unscaledFallbackCursor()
{
const uint defaultSize = defaultCursorSize();
if (!supportsCursorSizes({defaultSize}))
QSKIP("Default cursor size not supported");

const int screens = 4; // with scales 1, 2, 4, 8

exec([=] {
for (int i = 1; i < screens; ++i) {
OutputData d;
d.scale = int(qPow(2, i));
d.position = {1920 * i, 0};
add<Output>(d);
}
});

QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()->m_committed.buffer);
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.bufferScale, 1);
QSize unscaledPixelSize = exec([=] {
return pointer()->cursorSurface()->m_committed.buffer->size();
});

QCOMPARE(unscaledPixelSize.width(), int(defaultSize));
QCOMPARE(unscaledPixelSize.height(), int(defaultSize));

for (int i = 1; i < screens; ++i) {
exec([=] {
auto *surface = pointer()->cursorSurface();
surface->sendEnter(getAll<Output>()[i]);
surface->sendLeave(getAll<Output>()[i-1]);
});

xdgPingAndWaitForPong(); // Give the client a chance to mess up

// Surface size (buffer size / scale) should stay constant
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.buffer->size() / pointer()->cursorSurface()->m_committed.bufferScale, unscaledPixelSize);
}

// Remove the extra outputs to clean up for the next test
exec([&] { while (auto *o = output(1)) remove(o); });
}

void tst_seatv4::bitmapCursor()
{
// Add a highdpi output
Expand Down

0 comments on commit 021bd4d

Please sign in to comment.