qtbase/tests/auto/wayland/shared/coreprotocol.cpp
Johan Klokkhammer Helsing a5bcdd0f86 Client: Refactor cursors and fix various bugs
This patch is mostly a cleanup to prepare for implementations of
xcursor-configuration, but also fixes a couple of issues.

Most of the logic has now been moved out of QWaylandDisplay and QWaylandCursor
and into QWaylandInputDevice and QWaylandInputDevice::Pointer. QWaylandDisplay
now only contains mechanisms for avoiding loading the same theme multiple
times.

There is now only one setCursor method on QWaylandInputDevice, accepting a
QCursor and storing its values so changing scale factor doesn't require calling
setCursor again. QWaylandInputDevice::Pointer::updateCursor() is called
instead.

Cursor buffer scale is now set according to enter/leave events of the cursor
surface itself instead of the current window, this fixes incorrect buffer
scales for cursors on windows that span multiple outputs. The window buffer
scale can still be passed into the seat as a fallback until the first enter
event is received.

This also fixes a bug where the QWaylandBuffer of a bitmap cursor could be
deleted while it was being used as a cursor.

[ChangeLog][QPA plugin] Fixed a bug where the DPI of bitmap cursors were not
sent to the compositor, leading to the compositor incorrectly scaling the
cursor up or down.

[ChangeLog][QPA plugin] Fixed a bug where bitmap cursor hotspots were off when
the screen scale factor was different from the bitmap cursor device pixel
ratio.

Task-number: QTBUG-68571
Change-Id: I747a47ffff01b7b5f6a0ede3552ab37884c4fa60
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2019-02-15 09:12:35 +00:00

423 lines
14 KiB
C++

/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "coreprotocol.h"
#include "datadevice.h"
namespace MockCompositor {
void Surface::sendFrameCallbacks()
{
uint time = m_wlCompositor->m_compositor->currentTimeMilliseconds();
for (auto *callback : m_waitingFrameCallbacks)
callback->sendDone(time);
m_waitingFrameCallbacks.clear();
}
void Surface::sendEnter(Output *output)
{
m_outputs.append(output);
const auto outputResources = output->resourceMap().values(resource()->client());
for (auto outputResource: outputResources)
wl_surface::send_enter(resource()->handle, outputResource->handle);
}
void Surface::sendLeave(Output *output)
{
m_outputs.removeOne(output);
const auto outputResources = output->resourceMap().values(resource()->client());
for (auto outputResource: outputResources)
wl_surface::send_leave(resource()->handle, outputResource->handle);
}
void Surface::surface_destroy_resource(Resource *resource)
{
Q_UNUSED(resource);
for (auto *commit : m_commits)
delete commit->commitSpecific.frame;
bool removed = m_wlCompositor->m_surfaces.removeOne(this);
Q_ASSERT(removed);
delete this;
}
void Surface::surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y)
{
Q_UNUSED(resource);
QPoint offset(x, y);
m_pending.buffer = fromResource<Buffer>(buffer);
m_pending.commitSpecific.attachOffset = offset;
m_pending.commitSpecific.attached = true;
emit attach(buffer, offset);
}
void Surface::surface_set_buffer_scale(QtWaylandServer::wl_surface::Resource *resource, int32_t scale)
{
Q_UNUSED(resource);
m_pending.bufferScale = scale;
}
void Surface::surface_commit(Resource *resource)
{
Q_UNUSED(resource);
m_committed = m_pending;
m_commits.append(new DoubleBufferedState(m_committed));
if (auto *frame = m_pending.commitSpecific.frame)
m_waitingFrameCallbacks.append(frame);
m_pending.commitSpecific = PerCommitData();
emit commit();
if (m_committed.commitSpecific.attached)
emit bufferCommitted();
}
void Surface::surface_frame(Resource *resource, uint32_t callback)
{
// Although valid, there is really no point having multiple frame requests in the same commit.
// Make sure we don't do it
QCOMPARE(m_pending.commitSpecific.frame, nullptr);
auto *frame = new Callback(resource->client(), callback, 1);
m_pending.commitSpecific.frame = frame;
}
bool WlCompositor::isClean() {
for (auto *surface : qAsConst(m_surfaces)) {
if (!CursorRole::fromSurface(surface))
return false;
}
return true;
}
QString WlCompositor::dirtyMessage()
{
if (isClean())
return "clean";
QStringList messages;
for (auto *s : qAsConst(m_surfaces)) {
QString role = s->m_role ? s->m_role->staticMetaObject.className(): "none/unknown";
messages << "Surface with role: " + role;
}
return "Dirty, surfaces left:\n\t" + messages.join("\n\t");
}
void Output::sendGeometry()
{
const auto resources = resourceMap().values();
for (auto r : resources)
sendGeometry(r);
}
void Output::sendGeometry(Resource *resource)
{
// TODO: check resource version as well?
wl_output::send_geometry(resource->handle,
m_data.position.x(), m_data.position.y(),
m_data.physicalSize.width(), m_data.physicalSize.height(),
m_data.subpixel, m_data.make, m_data.model, m_data.transform);
}
void Output::sendScale(int factor)
{
Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION);
m_data.scale = factor;
const auto resources = resourceMap().values();
for (auto r : resources)
sendScale(r);
}
void Output::sendScale(Resource *resource)
{
Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION);
// TODO: check resource version as well?
wl_output::send_scale(resource->handle, m_data.scale);
}
void Output::sendDone()
{
Q_ASSERT(m_version >= WL_OUTPUT_DONE_SINCE_VERSION);
// TODO: check resource version as well?
const auto resources = resourceMap().values();
for (auto r : resources)
wl_output::send_done(r->handle);
}
void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource)
{
sendGeometry(resource);
send_mode(resource->handle, mode_preferred | mode_current,
m_data.mode.resolution.width(), m_data.mode.resolution.height(), m_data.mode.refreshRate);
if (m_version >= WL_OUTPUT_SCALE_SINCE_VERSION)
sendScale(resource);
if (m_version >= WL_OUTPUT_DONE_SINCE_VERSION)
wl_output::send_done(resource->handle);
}
// Seat stuff
Seat::Seat(CoreCompositor *compositor, uint capabilities, int version) //TODO: check version
: QtWaylandServer::wl_seat(compositor->m_display, version)
, m_compositor(compositor)
{
setCapabilities(capabilities);
}
Seat::~Seat()
{
qDeleteAll(m_oldPointers);
delete m_pointer;
}
void Seat::setCapabilities(uint capabilities) {
// TODO: Add support for touch
Q_ASSERT(~capabilities & capability_touch);
m_capabilities = capabilities;
if (m_capabilities & capability_pointer) {
if (!m_pointer)
m_pointer = (new Pointer(this));
} else if (m_pointer) {
m_oldPointers << m_pointer;
m_pointer = nullptr;
}
if (m_capabilities & capability_keyboard) {
if (!m_keyboard)
m_keyboard = (new Keyboard(this));
} else if (m_keyboard) {
m_oldKeyboards << m_keyboard;
m_keyboard = nullptr;
}
for (auto *resource : resourceMap())
wl_seat::send_capabilities(resource->handle, capabilities);
}
void Seat::seat_get_pointer(Resource *resource, uint32_t id)
{
if (~m_capabilities & capability_pointer) {
qWarning() << "Client requested a wl_pointer without the capability being available."
<< "This Could be a race condition when hotunplugging,"
<< "but is most likely a client error";
Pointer *pointer = new Pointer(this);
pointer->add(resource->client(), id, resource->version());
// TODO: mark as destroyed
m_oldPointers << pointer;
return;
}
m_pointer->add(resource->client(), id, resource->version());
}
void Seat::seat_get_keyboard(QtWaylandServer::wl_seat::Resource *resource, uint32_t id)
{
if (~m_capabilities & capability_pointer) {
qWarning() << "Client requested a wl_keyboard without the capability being available."
<< "This Could be a race condition when hotunplugging,"
<< "but is most likely a client error";
Keyboard *keyboard = new Keyboard(this);
keyboard->add(resource->client(), id, resource->version());
// TODO: mark as destroyed
m_oldKeyboards << keyboard;
return;
}
m_keyboard->add(resource->client(), id, resource->version());
}
Surface *Pointer::cursorSurface()
{
return m_cursorRole ? m_cursorRole->m_surface : nullptr;
}
uint Pointer::sendEnter(Surface *surface, const QPointF &position)
{
wl_fixed_t x = wl_fixed_from_double(position.x());
wl_fixed_t y = wl_fixed_from_double(position.y());
uint serial = m_seat->m_compositor->nextSerial();
m_enterSerials << serial;
wl_client *client = surface->resource()->client();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
wl_pointer::send_enter(r->handle, serial, surface->resource()->handle, x ,y);
return serial;
}
uint Pointer::sendLeave(Surface *surface)
{
uint serial = m_seat->m_compositor->nextSerial();
wl_client *client = surface->resource()->client();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
wl_pointer::send_leave(r->handle, serial, surface->resource()->handle);
return serial;
}
// Make sure you call enter, frame etc. first
void Pointer::sendMotion(wl_client *client, const QPointF &position)
{
wl_fixed_t x = wl_fixed_from_double(position.x());
wl_fixed_t y = wl_fixed_from_double(position.y());
auto time = m_seat->m_compositor->currentTimeMilliseconds();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_motion(r->handle, time, x, y);
}
// Make sure you call enter, frame etc. first
uint Pointer::sendButton(wl_client *client, uint button, uint state)
{
Q_ASSERT(state == button_state_pressed || state == button_state_released);
auto time = m_seat->m_compositor->currentTimeMilliseconds();
uint serial = m_seat->m_compositor->nextSerial();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_button(r->handle, serial, time, button, state);
return serial;
}
// Make sure you call enter, frame etc. first
void Pointer::sendAxis(wl_client *client, axis axis, qreal value)
{
auto time = m_seat->m_compositor->currentTimeMilliseconds();
wl_fixed_t val = wl_fixed_from_double(value);
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_axis(r->handle, time, axis, val);
}
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);
auto *s = fromResource<Surface>(surface);
QVERIFY(s);
if (s->m_role) {
auto *cursorRole = CursorRole::fromSurface(s);
QVERIFY(cursorRole);
QVERIFY(cursorRole == m_cursorRole);
} else {
m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
s->m_role = m_cursorRole;
}
// Directly checking the last serial would be racy, we may just have sent leaves/enters which
// the client hasn't yet seen. Instead just check if the serial matches an enter serial since
// the last time the client sent a set_cursor request.
QVERIFY(m_enterSerials.contains(serial));
while (m_enterSerials.first() < serial) { m_enterSerials.removeFirst(); }
m_hotspot = QPoint(hotspot_x, hotspot_y);
emit setCursor(serial);
}
uint Keyboard::sendEnter(Surface *surface)
{
auto serial = m_seat->m_compositor->nextSerial();
wl_client *client = surface->resource()->client();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_enter(r->handle, serial, surface->resource()->handle, QByteArray());
return serial;
}
uint Keyboard::sendLeave(Surface *surface)
{
auto serial = m_seat->m_compositor->nextSerial();
wl_client *client = surface->resource()->client();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_leave(r->handle, serial, surface->resource()->handle);
return serial;
}
uint Keyboard::sendKey(wl_client *client, uint key, uint state)
{
Q_ASSERT(state == key_state_pressed || state == key_state_released);
auto time = m_seat->m_compositor->currentTimeMilliseconds();
uint serial = m_seat->m_compositor->nextSerial();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_key(r->handle, serial, time, key, state);
return serial;
}
// Shm implementation
Shm::Shm(CoreCompositor *compositor, QVector<format> formats, int version)
: QtWaylandServer::wl_shm(compositor->m_display, version)
, m_compositor(compositor)
, m_formats(formats)
{
// Some formats are specified as mandatory
Q_ASSERT(m_formats.contains(format_argb8888));
Q_ASSERT(m_formats.contains(format_xrgb8888));
}
bool Shm::isClean()
{
// for (ShmPool *pool : qAsConst(m_pools)) {
// //TODO: return false if not cursor buffer
// if (pool->m_buffers.isEmpty()) {
// return false;
// }
// }
return true;
}
void Shm::shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size)
{
Q_UNUSED(fd);
Q_UNUSED(size);
auto *pool = new ShmPool(this, resource->client(), id, 1);
m_pools.append(pool);
}
ShmPool::ShmPool(Shm *shm, wl_client *client, int id, int version)
: QtWaylandServer::wl_shm_pool(client, id, version)
, m_shm(shm)
{
}
void ShmPool::shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format)
{
QSize size(width, height);
new ShmBuffer(offset, size, stride, Shm::format(format), resource->client(), id);
}
void ShmPool::shm_pool_destroy_resource(Resource *resource)
{
Q_UNUSED(resource);
bool removed = m_shm->m_pools.removeOne(this);
Q_ASSERT(removed);
delete this;
}
} // namespace MockCompositor