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>
423 lines
14 KiB
C++
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
|