Commit cb7a9456 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii Committed by Vlad Zahorodnii
Browse files

[wayland] Rework Xcursor theme loading code

Currently in order to load an Xcursor theme, kwin uses libwayland api,
which looks really awkward because of the way how the compositor talks
to itself via the internal connection.

The main motivation behind this change is to limit the usage of kwayland
client api in kwin.
parent 317bc74c
This diff is collapsed.
/*
* Copyright © 2002 Keith Packard
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef XCURSOR_H
#define XCURSOR_H
#ifdef __cplusplus
extern "C" {
#endif
typedef int XcursorBool;
typedef unsigned int XcursorUInt;
typedef XcursorUInt XcursorDim;
typedef XcursorUInt XcursorPixel;
typedef struct _XcursorImage {
XcursorUInt version; /* version of the image data */
XcursorDim size; /* nominal size for matching */
XcursorDim width; /* actual width */
XcursorDim height; /* actual height */
XcursorDim xhot; /* hot spot x (must be inside image) */
XcursorDim yhot; /* hot spot y (must be inside image) */
XcursorUInt delay; /* animation delay to next frame (ms) */
XcursorPixel *pixels; /* pointer to pixels */
} XcursorImage;
/*
* Other data structures exposed by the library API
*/
typedef struct _XcursorImages {
int nimage; /* number of images */
XcursorImage **images; /* array of XcursorImage pointers */
char *name; /* name used to load images */
} XcursorImages;
XcursorImages *
XcursorLibraryLoadImages (const char *file, const char *theme, int size);
void
XcursorImagesDestroy (XcursorImages *images);
void
xcursor_load_theme(const char *theme, int size,
void (*load_callback)(XcursorImages *, void *),
void *user_data);
#ifdef __cplusplus
}
#endif
#endif
......@@ -424,6 +424,7 @@ add_subdirectory(helpers)
########### next target ###############
set(kwin_SRCS
3rdparty/xcursor.c
abstract_client.cpp
abstract_opengl_context_attribute_builder.cpp
abstract_output.cpp
......@@ -490,8 +491,8 @@ set(kwin_SRCS
pointer_input.cpp
popup_input_filter.cpp
rootinfo_filter.cpp
rules.cpp
rulebooksettings.cpp
rules.cpp
scene.cpp
screenedge.cpp
screenlockerwatcher.cpp
......@@ -524,7 +525,6 @@ set(kwin_SRCS
virtualkeyboard.cpp
virtualkeyboard_dbus.cpp
was_user_interaction_x11_filter.cpp
wayland_cursor_theme.cpp
wayland_server.cpp
waylandclient.cpp
waylandshellintegration.cpp
......@@ -534,6 +534,7 @@ set(kwin_SRCS
x11client.cpp
x11eventfilter.cpp
xcbutils.cpp
xcursortheme.cpp
xdgshellclient.cpp
xkb.cpp
xwaylandclient.cpp
......
......@@ -27,9 +27,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "options.h"
#include "screenedge.h"
#include "screens.h"
#include "wayland_cursor_theme.h"
#include "wayland_server.h"
#include "workspace.h"
#include "xcursortheme.h"
#include <kwineffects.h>
#include <KWayland/Client/buffer.h>
......@@ -52,46 +52,53 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
namespace KWin
{
template <typename T>
PlatformCursorImage loadReferenceThemeCursor(const T &shape)
static PlatformCursorImage loadReferenceThemeCursor_helper(const KXcursorTheme &theme,
const QByteArray &name)
{
if (!waylandServer()->internalShmPool()) {
const QVector<KXcursorSprite> sprites = theme.shape(name);
if (sprites.isEmpty()) {
return PlatformCursorImage();
}
QScopedPointer<WaylandCursorTheme> cursorTheme;
cursorTheme.reset(new WaylandCursorTheme(waylandServer()->internalShmPool()));
QImage cursorImage = sprites.first().data();
cursorImage.setDevicePixelRatio(theme.devicePixelRatio());
wl_cursor_image *cursor = cursorTheme->get(shape);
if (!cursor) {
return PlatformCursorImage();
}
QPoint cursorHotspot = sprites.first().hotspot();
cursorHotspot /= theme.devicePixelRatio();
return PlatformCursorImage(cursorImage, cursorHotspot);
}
static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name)
{
const Cursor *pointerCursor = Cursors::self()->mouse();
wl_buffer *b = wl_cursor_image_get_buffer(cursor);
if (!b) {
const KXcursorTheme theme = KXcursorTheme::fromTheme(pointerCursor->themeName(),
pointerCursor->themeSize(),
screens()->maxScale());
if (theme.isEmpty()) {
return PlatformCursorImage();
}
waylandServer()->internalClientConection()->flush();
waylandServer()->dispatch();
auto buffer = KWaylandServer::BufferInterface::get(
waylandServer()->internalConnection()->getResource(
KWayland::Client::Buffer::getId(b)));
if (!buffer) {
return PlatformCursorImage{};
PlatformCursorImage platformCursorImage = loadReferenceThemeCursor_helper(theme, name);
if (!platformCursorImage.isNull()) {
return platformCursorImage;
}
const qreal scale = screens()->maxScale();
QImage image = buffer->data().copy();
image.setDevicePixelRatio(scale);
const QVector<QByteArray> alternativeNames = Cursor::cursorAlternativeNames(name);
for (const QByteArray &alternativeName : alternativeNames) {
platformCursorImage = loadReferenceThemeCursor_helper(theme, alternativeName);
if (!platformCursorImage.isNull()) {
break;
}
}
const QPoint hotSpot(
qRound(cursor->hotspot_x / scale),
qRound(cursor->hotspot_y / scale)
);
return platformCursorImage;
}
return PlatformCursorImage(image, hotSpot);
static PlatformCursorImage loadReferenceThemeCursor(const CursorShape &shape)
{
return loadReferenceThemeCursor(shape.name());
}
static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0");
......@@ -1526,7 +1533,7 @@ void PointerInputTest::testResizeCursor()
Cursors::self()->mouse()->setPos(cursorPos);
const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor);
QVERIFY(!arrowCursor.image().isNull());
QVERIFY(!arrowCursor.isNull());
QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image());
QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot());
......@@ -1538,7 +1545,7 @@ void PointerInputTest::testResizeCursor()
QFETCH(KWin::CursorShape, cursorShape);
const PlatformCursorImage resizeCursor = loadReferenceThemeCursor(cursorShape);
QVERIFY(!resizeCursor.image().isNull());
QVERIFY(!resizeCursor.isNull());
QCOMPARE(kwinApp()->platform()->cursorImage().image(), resizeCursor.image());
QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), resizeCursor.hotSpot());
......@@ -1577,7 +1584,7 @@ void PointerInputTest::testMoveCursor()
Cursors::self()->mouse()->setPos(c->frameGeometry().center());
const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor);
QVERIFY(!arrowCursor.image().isNull());
QVERIFY(!arrowCursor.isNull());
QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image());
QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot());
......@@ -1588,7 +1595,7 @@ void PointerInputTest::testMoveCursor()
QVERIFY(c->isMove());
const PlatformCursorImage sizeAllCursor = loadReferenceThemeCursor(Qt::SizeAllCursor);
QVERIFY(!sizeAllCursor.image().isNull());
QVERIFY(!sizeAllCursor.isNull());
QCOMPARE(kwinApp()->platform()->cursorImage().image(), sizeAllCursor.image());
QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), sizeAllCursor.hotSpot());
......
......@@ -98,8 +98,8 @@ Cursor::Cursor(QObject *parent)
: QObject(parent)
, m_mousePollingCounter(0)
, m_cursorTrackingCounter(0)
, m_themeName("default")
, m_themeSize(24)
, m_themeName(defaultThemeName())
, m_themeSize(defaultThemeSize())
{
loadThemeSettings();
QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"),
......@@ -128,8 +128,8 @@ void Cursor::loadThemeSettings()
void Cursor::loadThemeFromKConfig()
{
KConfigGroup mousecfg(kwinApp()->inputConfig(), "Mouse");
const QString themeName = mousecfg.readEntry("cursorTheme", "default");
const uint themeSize = mousecfg.readEntry("cursorSize", 24);
const QString themeName = mousecfg.readEntry("cursorTheme", defaultThemeName());
const uint themeSize = mousecfg.readEntry("cursorSize", defaultThemeSize());
updateTheme(themeName, themeSize);
}
......@@ -277,7 +277,7 @@ void Cursor::doStopCursorTracking()
{
}
QVector<QByteArray> Cursor::cursorAlternativeNames(const QByteArray &name) const
QVector<QByteArray> Cursor::cursorAlternativeNames(const QByteArray &name)
{
static const QHash<QByteArray, QVector<QByteArray>> alternatives = {
{QByteArrayLiteral("left_ptr"), {QByteArrayLiteral("arrow"),
......@@ -409,6 +409,16 @@ QVector<QByteArray> Cursor::cursorAlternativeNames(const QByteArray &name) const
return QVector<QByteArray>();
}
QString Cursor::defaultThemeName()
{
return QStringLiteral("default");
}
int Cursor::defaultThemeSize()
{
return 24;
}
QByteArray CursorShape::name() const
{
switch (m_shape) {
......
......@@ -139,7 +139,15 @@ public:
/**
* @return list of alternative names for the cursor with @p name
*/
QVector<QByteArray> cursorAlternativeNames(const QByteArray &name) const;
static QVector<QByteArray> cursorAlternativeNames(const QByteArray &name);
/**
* Returns the default Xcursor theme name.
*/
static QString defaultThemeName();
/**
* Returns the default Xcursor theme size.
*/
static int defaultThemeSize();
/**
* Returns the current cursor position. This method does an update of the mouse position if
......
......@@ -226,6 +226,9 @@ public:
}
virtual ~PlatformCursorImage() = default;
bool isNull() const {
return m_image.isNull();
}
QImage image() const {
return m_image;
}
......
......@@ -55,7 +55,6 @@ class Scene;
class Screens;
class ScreenEdges;
class Toplevel;
class WaylandCursorTheme;
namespace Decoration
{
......
......@@ -34,7 +34,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "outputscreens.h"
#include "pointer_input.h"
#include "screens.h"
#include "wayland_cursor_theme.h"
#include "wayland_server.h"
#include <config-kwin.h>
......
......@@ -66,8 +66,6 @@ class XdgShell;
namespace KWin
{
class WaylandCursorTheme;
namespace Wayland
{
......
......@@ -27,15 +27,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "input_event_spy.h"
#include "osd.h"
#include "screens.h"
#include "wayland_cursor_theme.h"
#include "wayland_server.h"
#include "workspace.h"
#include "decorations/decoratedclient.h"
// KDecoration
#include <KDecoration2/Decoration>
// KWayland
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/buffer.h>
#include <KWaylandServer/buffer_interface.h>
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/display.h>
......@@ -1119,23 +1116,6 @@ void CursorImage::updateServerCursor()
}
}
void WaylandCursorImage::loadTheme()
{
if (m_cursorTheme) {
return;
}
// check whether we can create it
if (waylandServer()->internalShmPool()) {
m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this);
connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this,
[this] {
delete m_cursorTheme;
m_cursorTheme = nullptr;
}
);
}
}
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
{
loadThemeCursor(shape, &m_effectsCursor);
......@@ -1268,36 +1248,83 @@ void CursorImage::loadThemeCursor(const QByteArray &shape, WaylandCursorImage::I
m_waylandImage.loadThemeCursor(shape, image);
}
template <typename T>
void WaylandCursorImage::loadThemeCursor(const T &shape, Image *image)
WaylandCursorImage::WaylandCursorImage(QObject *parent)
: QObject(parent)
{
loadTheme();
if (!m_cursorTheme) {
return;
Cursor *pointerCursor = Cursors::self()->mouse();
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::invalidateCursorTheme);
connect(screens(), &Screens::maxScaleChanged, this, &WaylandCursorImage::invalidateCursorTheme);
}
bool WaylandCursorImage::ensureCursorTheme()
{
if (!m_cursorTheme.isEmpty()) {
return true;
}
image->image = {};
wl_cursor_image *cursor = m_cursorTheme->get(shape);
if (!cursor) {
qDebug() << "Could not find cursor" << shape;
return;
const Cursor *pointerCursor = Cursors::self()->mouse();
const qreal targetDevicePixelRatio = screens()->maxScale();
m_cursorTheme = KXcursorTheme::fromTheme(pointerCursor->themeName(), pointerCursor->themeSize(),
targetDevicePixelRatio);
if (!m_cursorTheme.isEmpty()) {
return true;
}
m_cursorTheme = KXcursorTheme::fromTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(),
targetDevicePixelRatio);
if (!m_cursorTheme.isEmpty()) {
return true;
}
wl_buffer *b = wl_cursor_image_get_buffer(cursor);
if (!b) {
return false;
}
void WaylandCursorImage::invalidateCursorTheme()
{
m_cursorTheme = KXcursorTheme();
}
void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, Image *cursorImage)
{
loadThemeCursor(shape.name(), cursorImage);
}
void WaylandCursorImage::loadThemeCursor(const QByteArray &name, Image *cursorImage)
{
if (!ensureCursorTheme()) {
return;
}
waylandServer()->internalClientConection()->flush();
waylandServer()->dispatch();
auto buffer = KWaylandServer::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b)));
if (!buffer) {
if (loadThemeCursor_helper(name, cursorImage)) {
return;
}
auto scale = screens()->maxScale();
int hotSpotX = qRound(cursor->hotspot_x / scale);
int hotSpotY = qRound(cursor->hotspot_y / scale);
QImage img = buffer->data().copy();
img.setDevicePixelRatio(scale);
*image = {img, QPoint(hotSpotX, hotSpotY)};
const auto alternativeNames = Cursor::cursorAlternativeNames(name);
for (const QByteArray &alternativeName : alternativeNames) {
if (loadThemeCursor_helper(alternativeName, cursorImage)) {
return;
}
}
qCWarning(KWIN_CORE) << "Failed to load theme cursor for shape" << name;
}
bool WaylandCursorImage::loadThemeCursor_helper(const QByteArray &name, Image *cursorImage)
{
const QVector<KXcursorSprite> sprites = m_cursorTheme.shape(name);
if (sprites.isEmpty()) {
return false;
}
cursorImage->image = sprites.first().data();
cursorImage->image.setDevicePixelRatio(m_cursorTheme.devicePixelRatio());
cursorImage->hotspot = sprites.first().hotspot();
cursorImage->hotspot /= m_cursorTheme.devicePixelRatio();
return true;
}
void CursorImage::reevaluteSource()
......
......@@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "input.h"
#include "cursor.h"
#include "xcursortheme.h"
#include <QElapsedTimer>
#include <QObject>
......@@ -42,7 +43,6 @@ namespace KWin
class CursorImage;
class InputRedirection;
class Toplevel;
class WaylandCursorTheme;
class CursorShape;
namespace Decoration
......@@ -181,19 +181,25 @@ class WaylandCursorImage : public QObject
{
Q_OBJECT
public:
void loadTheme();
explicit WaylandCursorImage(QObject *parent = nullptr);
struct Image {
QImage image;
QPoint hotspot;
};
template <typename T>
void loadThemeCursor(const T &shape, Image *image);
void loadThemeCursor(const CursorShape &shape, Image *cursorImage);
void loadThemeCursor(const QByteArray &name, Image *cursorImage);
Q_SIGNALS:
void themeChanged();
private:
WaylandCursorTheme *m_cursorTheme = nullptr;
bool loadThemeCursor_helper(const QByteArray &name, Image *cursorImage);
bool ensureCursorTheme();
void invalidateCursorTheme();
KXcursorTheme m_cursorTheme;
};
class CursorImage : public QObject
......
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "wayland_cursor_theme.h"
#include "cursor.h"
#include "wayland_server.h"
#include "screens.h"
// Qt
#include <QVector>
// KWayland
#include <KWayland/Client/shm_pool.h>
#include <KWaylandServer/display.h>
#include <KWaylandServer/output_interface.h>
// Wayland
#include <wayland-cursor.h>
namespace KWin
{
WaylandCursorTheme::WaylandCursorTheme(KWayland::Client::ShmPool *shm, QObject *parent)
: QObject(parent)
, m_theme(nullptr)
, m_shm(shm)
{
connect(screens(), &Screens::maxScaleChanged, this, &WaylandCursorTheme::loadTheme);
}
WaylandCursorTheme::~WaylandCursorTheme()
{
destroyTheme();
}
void WaylandCursorTheme::loadTheme()
{
if (!m_shm->isValid()) {
return;
}
Cursor *c = Cursors::self()->mouse();
int size = c->themeSize();
if (size == 0) {
//set a default size
size = 24;
}
size *= screens()->maxScale();
auto theme = wl_cursor_theme_load(c->themeName().toUtf8().constData(),
size, m_shm->shm());
if (theme) {
if (!m_theme) {
// so far the theme had not been created, this means we need to start tracking theme changes
connect(c, &Cursor::themeChanged, this, &WaylandCursorTheme::loadTheme);
} else {
destroyTheme();
}
m_theme = theme;
emit themeChanged();
}
}
void WaylandCursorTheme::destroyTheme()
{
if (!m_theme) {
return;
}
wl_cursor_theme_destroy(m_theme);
m_theme = nullptr;
}
wl_cursor_image *WaylandCursorTheme::get(CursorShape shape)
{