diff --git a/CMakeLists.txt b/CMakeLists.txt index 043ac7b7..c776f193 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ endif() if(FEATURE_aurora_xkbcommon) add_subdirectory(src/platformsupport/xkbcommon) endif() +add_subdirectory(src/platform) add_subdirectory(src/compositor) if(FEATURE_aurora_brcm) add_subdirectory(src/plugins/hardwareintegration/compositor/brcm-egl) diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt new file mode 100644 index 00000000..29578fca --- /dev/null +++ b/src/platform/CMakeLists.txt @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +# SPDX-License-Identifier: BSD-3-Clause + +include(ECMQtDeclareLoggingCategory) +ecm_qt_declare_logging_category( + AuroraPlatform_SOURCES + HEADER "auroracoreloggingcategories.h" + IDENTIFIER "Aurora::Platform::gLcAuroraPlatform" + CATEGORY_NAME "aurora.platform" + DEFAULT_SEVERITY "Info" + DESCRIPTION "Aurora platform abstraction" +) + +liri_add_module(AuroraPlatform + DESCRIPTION + "Platform abstraction library for Wayland compositors using Aurora" + SOURCES + deviceintegration.cpp deviceintegration.h + deviceintegrationplugin.cpp deviceintegrationplugin.h + eglconfigchooser.cpp eglconfigchooser_p.h + inputdevice.cpp inputdevice.h + inputmanager.cpp inputmanager.h + keyboarddevice.cpp keyboarddevice.h + output.cpp output.h output_p.h + pointerdevice.cpp pointerdevice.h + session.cpp session.h + session_noop.cpp session_noop_p.h + touchdevice.cpp touchdevice.h + window.cpp window.h window_p.h + xcursor.c xcursor.h + xcursortheme.cpp xcursortheme.h + ${AuroraPlatform_SOURCES} + DEFINES + QT_NO_CAST_FROM_ASCII + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui +) + +liri_finalize_module(AuroraPlatform) diff --git a/src/platform/deviceintegration.cpp b/src/platform/deviceintegration.cpp new file mode 100644 index 00000000..c7de1c92 --- /dev/null +++ b/src/platform/deviceintegration.cpp @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "deviceintegration.h" +#include "deviceintegration_p.h" +#include "eglconfigchooser_p.h" +#include "inputmanager.h" + +namespace Aurora { + +namespace Platform { + +/* + * DeviceIntegration + */ + +DeviceIntegration::DeviceIntegration(QObject *parent) + : QObject(parent) + , d_ptr(new DeviceIntegrationPrivate(this)) +{ +} + +DeviceIntegration::~DeviceIntegration() +{ +} + +DeviceIntegration::Status DeviceIntegration::status() const +{ + Q_D(const DeviceIntegration); + return d->status; +} + +void DeviceIntegration::setStatus(Status status) +{ + Q_D(DeviceIntegration); + + if (d->status == status) + return; + + d->status = status; + emit statusChanged(status); +} + +bool DeviceIntegration::supportsPBuffers() +{ + return true; +} + +bool DeviceIntegration::supportsSurfacelessContexts() +{ + return true; +} + +EGLNativeDisplayType DeviceIntegration::platformDisplay() const +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLDisplay DeviceIntegration::createDisplay(EGLNativeDisplayType nativeDisplay) +{ + return eglGetDisplay(nativeDisplay); +} + +void DeviceIntegration::destroyDisplay(EGLDisplay eglDisplay) +{ + if (eglDisplay != EGL_NO_DISPLAY) + eglTerminate(eglDisplay); +} + +EGLNativeWindowType DeviceIntegration::createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format) +{ + Q_UNUSED(window) + Q_UNUSED(size) + Q_UNUSED(format) + return 0; +} + +void DeviceIntegration::destroyNativeWindow(EGLNativeWindowType nativeWindow) +{ + Q_UNUSED(nativeWindow) +} + +QSurfaceFormat DeviceIntegration::surfaceFormatFor(const QSurfaceFormat &inputFormat) const +{ + return inputFormat; +} + +EGLint DeviceIntegration::surfaceType() const +{ + return EGL_WINDOW_BIT; +} + +EGLConfig DeviceIntegration::chooseConfig(EGLDisplay display, const QSurfaceFormat &format) +{ + Q_D(DeviceIntegration); + + QVector configAttribs = eglConfigAttributesFromSurfaceFormat(display, format); + + configAttribs.append(EGL_SURFACE_TYPE); + configAttribs.append(surfaceType()); + + configAttribs.append(EGL_NONE); + + // Get the number of matching configurations for the attributes + EGLConfig config = nullptr; + EGLint numConfigs = 0; + if (!eglChooseConfig(display, configAttribs.constData(), &config, 1, &numConfigs)) + return nullptr; + return config; +} + +Window *DeviceIntegration::getWindow(QWindow *qtWindow) const +{ + Q_UNUSED(qtWindow) + return nullptr; +} + +InputManager *Aurora::Platform::DeviceIntegration::createInputManager() +{ + return nullptr; +} + +/* + * DeviceIntegrationPrivate + */ + +DeviceIntegrationPrivate::DeviceIntegrationPrivate(DeviceIntegration *self) + : q_ptr(self) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/deviceintegration.h b/src/platform/deviceintegration.h new file mode 100644 index 00000000..eeb81168 --- /dev/null +++ b/src/platform/deviceintegration.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +#include + +#include + +class QPlatformSurface; + +namespace Aurora { + +namespace Platform { + +class DeviceIntegrationPrivate; +class InputManager; +class Window; + +class LIRIAURORAPLATFORM_EXPORT DeviceIntegration : public QObject +{ + Q_OBJECT + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_DECLARE_PRIVATE(DeviceIntegration) +public: + enum class Status { + NotReady, + Ready, + Failed + }; + Q_ENUM(Status) + + ~DeviceIntegration(); + + Status status() const; + + virtual void initialize() = 0; + virtual void destroy() = 0; + + virtual bool supportsPBuffers(); + virtual bool supportsSurfacelessContexts(); + + virtual EGLNativeDisplayType platformDisplay() const; + + virtual EGLDisplay createDisplay(EGLNativeDisplayType nativeDisplay); + virtual void destroyDisplay(EGLDisplay eglDisplay); + + virtual EGLNativeWindowType createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format); + virtual void destroyNativeWindow(EGLNativeWindowType nativeWindow); + + virtual QSurfaceFormat surfaceFormatFor(const QSurfaceFormat &inputFormat) const; + virtual EGLint surfaceType() const; + + virtual EGLConfig chooseConfig(EGLDisplay display, const QSurfaceFormat &format); + + virtual Window *createWindow(Output *output, QWindow *qtWindow) = 0; + virtual Window *getWindow(QWindow *qtWindow) const; + + virtual void waitForVSync(Window *window) const = 0; + virtual void presentBuffer(Window *window) = 0; + + virtual InputManager *createInputManager(); + + virtual Outputs outputs() const = 0; + +signals: + void statusChanged(Status status); + void outputAdded(Output *output); + void outputRemoved(Output *output); + +protected: + QScopedPointer const d_ptr; + + explicit DeviceIntegration(QObject *parent = nullptr); + + void setStatus(Status status); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/deviceintegration_p.h b/src/platform/deviceintegration_p.h new file mode 100644 index 00000000..e9c56d50 --- /dev/null +++ b/src/platform/deviceintegration_p.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Aurora API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT DeviceIntegrationPrivate +{ + Q_DECLARE_PUBLIC(DeviceIntegration) +public: + DeviceIntegrationPrivate(DeviceIntegration *self); + + DeviceIntegration::Status status = DeviceIntegration::Status::NotReady; + +protected: + DeviceIntegration *q_ptr = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/deviceintegrationplugin.cpp b/src/platform/deviceintegrationplugin.cpp new file mode 100644 index 00000000..055a80ad --- /dev/null +++ b/src/platform/deviceintegrationplugin.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#include +#include +#include + +#include "auroracoreloggingcategories.h" +#include "deviceintegrationplugin.h" + +namespace Aurora { + +namespace Platform { + +DeviceIntegrationPlugin::DeviceIntegrationPlugin(QObject *parent) + : QObject(parent) +{ +} + +QStringList DeviceIntegrationFactory::keys(const QString &pluginPath) +{ + QStringList list; + + if (!pluginPath.isEmpty()) + QCoreApplication::addLibraryPath(pluginPath); + + const auto paths = QCoreApplication::libraryPaths(); + for (const auto &path : paths) { + const auto absolutePath = + QDir(path).absoluteFilePath(QStringLiteral("aurora/deviceintegration")); + QDir dir(absolutePath); + + const auto fileNames = dir.entryList(QDir::Files); + for (const auto &fileName : fileNames) { + QPluginLoader loader(dir.absoluteFilePath(fileName)); + + if (loader.load()) { + const auto metaData = + loader.metaData().value(QLatin1String("MetaData")).toVariant().toMap(); + list += metaData.value(QStringLiteral("Keys"), QStringList()).toStringList(); + } + + loader.unload(); + } + } + + qCDebug(gLcAuroraPlatform) << "Device integration plugin keys:" << list; + return list; +} + +DeviceIntegration *DeviceIntegrationFactory::create(const QString &name, const QString &pluginPath) +{ + if (!pluginPath.isEmpty()) + QCoreApplication::addLibraryPath(pluginPath); + + const auto paths = QCoreApplication::libraryPaths(); + for (const auto &path : paths) { + const auto absolutePath = + QDir(path).absoluteFilePath(QStringLiteral("aurora/deviceintegration")); + QDir dir(absolutePath); + + const auto fileNames = dir.entryList(QDir::Files); + for (const auto &fileName : fileNames) { + QPluginLoader loader(dir.absoluteFilePath(fileName)); + + if (loader.load()) { + const auto metaData = + loader.metaData().value(QLatin1String("MetaData")).toVariant().toMap(); + const auto keys = + metaData.value(QStringLiteral("Keys"), QStringList()).toStringList(); + + if (keys.contains(name)) { + auto *plugin = dynamic_cast(loader.instance()); + if (plugin) + return plugin->create(); + } + } + + loader.unload(); + } + } + + return nullptr; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/deviceintegrationplugin.h b/src/platform/deviceintegrationplugin.h new file mode 100644 index 00000000..c705bd87 --- /dev/null +++ b/src/platform/deviceintegrationplugin.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class DeviceIntegration; + +class LIRIAURORAPLATFORM_EXPORT DeviceIntegrationPlugin : public QObject +{ + Q_OBJECT +public: + explicit DeviceIntegrationPlugin(QObject *parent = nullptr); + + virtual DeviceIntegration *create() = 0; +}; + +class LIRIAURORAPLATFORM_EXPORT DeviceIntegrationFactory +{ +public: + static QStringList keys(const QString &pluginPath = QString()); + static DeviceIntegration *create(const QString &name, const QString &pluginPath = QString()); +}; + +} // namespace Platform + +} // namespace Aurora + +Q_DECLARE_INTERFACE(Aurora::Platform::DeviceIntegrationPlugin, + "io.liri.Aurora.DeviceIntegrationPlugin/1") diff --git a/src/platform/eglconfigchooser.cpp b/src/platform/eglconfigchooser.cpp new file mode 100644 index 00000000..a57318d5 --- /dev/null +++ b/src/platform/eglconfigchooser.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "eglconfigchooser_p.h" + +#ifndef EGL_OPENGL_ES3_BIT_KHR +# define EGL_OPENGL_ES3_BIT_KHR 0x0040 +#endif + +namespace Aurora { + +namespace Platform { + +bool hasEglExtension(EGLDisplay display, const char *name) +{ + QList extensions = + QByteArray(reinterpret_cast(eglQueryString(display, EGL_EXTENSIONS))) + .split(' '); + return extensions.contains(name); +} + +QVector eglConfigAttributesFromSurfaceFormat(EGLDisplay display, + const QSurfaceFormat &format) +{ + QVector configAttribs; + + configAttribs.append(EGL_RED_SIZE); + configAttribs.append(qMax(0, format.redBufferSize())); + + configAttribs.append(EGL_GREEN_SIZE); + configAttribs.append(qMax(0, format.greenBufferSize())); + + configAttribs.append(EGL_BLUE_SIZE); + configAttribs.append(qMax(0, format.blueBufferSize())); + + configAttribs.append(EGL_ALPHA_SIZE); + configAttribs.append(qMax(0, format.alphaBufferSize())); + + configAttribs.append(EGL_SAMPLES); + configAttribs.append(qMax(0, format.samples())); + + configAttribs.append(EGL_SAMPLE_BUFFERS); + configAttribs.append(format.samples() > 0 ? 1 : 0); + + switch (format.renderableType()) { + case QSurfaceFormat::OpenGL: + configAttribs.append(EGL_RENDERABLE_TYPE); + configAttribs.append(EGL_OPENGL_BIT); + break; + case QSurfaceFormat::OpenGLES: + configAttribs.append(EGL_RENDERABLE_TYPE); + if (format.majorVersion() == 1) + configAttribs.append(EGL_OPENGL_ES_BIT); + else if (format.majorVersion() == 2) + configAttribs.append(EGL_OPENGL_ES2_BIT); + else if (format.majorVersion() == 3 && hasEglExtension(display, "EGL_KHR_create_context")) + configAttribs.append(EGL_OPENGL_ES3_BIT_KHR); + else if (format.majorVersion() == 3) + configAttribs.append(EGL_OPENGL_ES3_BIT); + break; + case QSurfaceFormat::OpenVG: + configAttribs.append(EGL_RENDERABLE_TYPE); + configAttribs.append(EGL_OPENVG_BIT); + break; + default: + break; + } + + if (format.renderableType() != QSurfaceFormat::OpenVG) { + configAttribs.append(EGL_DEPTH_SIZE); + configAttribs.append(qMax(0, format.depthBufferSize())); + + configAttribs.append(EGL_STENCIL_SIZE); + configAttribs.append(qMax(0, format.stencilBufferSize())); + } else { + configAttribs.append(EGL_ALPHA_MASK_SIZE); + configAttribs.append(8); + } + + return configAttribs; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/eglconfigchooser_p.h b/src/platform/eglconfigchooser_p.h new file mode 100644 index 00000000..71532feb --- /dev/null +++ b/src/platform/eglconfigchooser_p.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +bool hasEglExtension(EGLDisplay display, const char *name); +QVector eglConfigAttributesFromSurfaceFormat(EGLDisplay display, + const QSurfaceFormat &format); + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputdevice.cpp b/src/platform/inputdevice.cpp new file mode 100644 index 00000000..c5c3593c --- /dev/null +++ b/src/platform/inputdevice.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "inputdevice.h" + +namespace Aurora { + +namespace Platform { + +InputDevice::InputDevice(QObject *parent) + : QObject(parent) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputdevice.h b/src/platform/inputdevice.h new file mode 100644 index 00000000..d2e9066e --- /dev/null +++ b/src/platform/inputdevice.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT InputDevice : public QObject +{ + Q_OBJECT + Q_PROPERTY(DeviceType deviceType READ deviceType CONSTANT) +public: + enum class DeviceType { + Unknown, + Pointer, + Keyboard, + Touch, + Tablet + }; + Q_ENUM(DeviceType) + + explicit InputDevice(QObject *parent = nullptr); + + virtual QString seatName() const = 0; + + virtual DeviceType deviceType() = 0; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputmanager.cpp b/src/platform/inputmanager.cpp new file mode 100644 index 00000000..3288c37c --- /dev/null +++ b/src/platform/inputmanager.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "inputmanager.h" + +namespace Aurora { + +namespace Platform { + +InputManager::InputManager(QObject *parent) + : QObject(parent) +{ +} + +QList InputManager::keyboardDevices() const +{ + return QList(); +} + +QList InputManager::pointerDevices() const +{ + return QList(); +} + +QList InputManager::touchDevices() const +{ + return QList(); +} + +int InputManager::deviceCount(InputDevice::DeviceType deviceType) const +{ + Q_UNUSED(deviceType) + return 0; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputmanager.h b/src/platform/inputmanager.h new file mode 100644 index 00000000..e1b6f591 --- /dev/null +++ b/src/platform/inputmanager.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class KeyboardDevice; +class PointerDevice; +class TouchDevice; + +class LIRIAURORAPLATFORM_EXPORT InputManager : public QObject +{ + Q_OBJECT +public: + explicit InputManager(QObject *parent = nullptr); + + virtual QList keyboardDevices() const; + virtual QList pointerDevices() const; + virtual QList touchDevices() const; + + virtual int deviceCount(InputDevice::DeviceType deviceType) const; + +signals: + void deviceAdded(InputDevice *inputDevice); + void deviceRemoved(InputDevice *inputDevice); + void keyboardAdded(KeyboardDevice *keyboardDevice); + void keyboardRemoved(KeyboardDevice *keyboardDevice); + void pointerAdded(PointerDevice *pointerDevice); + void pointerRemoved(PointerDevice *pointerDevice); + void touchAdded(TouchDevice *touchDevice); + void touchRemoved(TouchDevice *touchDevice); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/keyboarddevice.cpp b/src/platform/keyboarddevice.cpp new file mode 100644 index 00000000..34aed6cb --- /dev/null +++ b/src/platform/keyboarddevice.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "keyboarddevice.h" + +namespace Aurora { + +namespace Platform { + +KeyboardDevice::KeyboardDevice(QObject *parent) + : InputDevice(parent) +{ +} + +InputDevice::DeviceType KeyboardDevice::deviceType() +{ + return DeviceType::Keyboard; +} + +bool KeyboardDevice::isKeyRepeatEnabled() const +{ + return false; +} + +qint32 KeyboardDevice::keyRepeatRate() const +{ + return 0; +} + +qint32 KeyboardDevice::keyRepeatDelay() const +{ + return 0; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/keyboarddevice.h b/src/platform/keyboarddevice.h new file mode 100644 index 00000000..66a37547 --- /dev/null +++ b/src/platform/keyboarddevice.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT KeyboardDevice : public InputDevice +{ + Q_OBJECT + Q_PROPERTY(bool keyRepeatEnabled READ isKeyRepeatEnabled NOTIFY keyRepeatChanged) + Q_PROPERTY(qint32 keyRepeatRate READ keyRepeatRate NOTIFY keyRepeatChanged) + Q_PROPERTY(qint32 keyRepeatDelay READ keyRepeatDelay NOTIFY keyRepeatChanged) +public: + explicit KeyboardDevice(QObject *parent = nullptr); + + DeviceType deviceType() override; + + virtual bool isKeyRepeatEnabled() const; + virtual qint32 keyRepeatRate() const; + virtual qint32 keyRepeatDelay() const; + +signals: + void keyPressed(quint32 key); + void keyReleased(quint32 key); + void keyRepeatChanged(); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/output.cpp b/src/platform/output.cpp new file mode 100644 index 00000000..a962288c --- /dev/null +++ b/src/platform/output.cpp @@ -0,0 +1,453 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include + +#include "output.h" +#include "output_p.h" + +namespace Aurora { + +namespace Platform { + +/*! + \class Output + \inmodule AuroraCore + \brief Generic output representation. + + The Output class represents an output. + */ + +/*! + * Constructs an Output with the given \a parent. + */ +Output::Output(QObject *parent) + : QObject(parent) + , d_ptr(new OutputPrivate(this)) +{ +} + +Output::~Output() +{ +} + +/*! + * \property Output::name + * \brief A user presentable string representing the output. + * + * This property contains a user presentable string representing the output, + * typycally something like "VGA1", "eDP-1", "HDMI1", etc. + */ +QString Output::name() const +{ + return QString(); +} + +/*! + * \property Output::description + * \brief Human readable description of the output. + * + * This property contains a human readable description of he output. + */ +QString Output::description() const +{ + return QString(); +} + +/*! + * \property Output::uuid + * \brief The unique identifier of the output. + * + * This property contains a unique identifier of the output. + */ +QUuid Output::uuid() const +{ + Q_D(const Output); + return d->uuid; +} + +/*! + * \property Output::manufacturer + * \brief The manufacturer of the screen. + * + * This property holds the manufacturer of the screen. + */ +QString Output::manufacturer() const +{ + Q_D(const Output); + return d->manufacturer; +} + +/*! + * \property Output::model + * \brief The model of the screen. + * + * This property holds the model of the screen. + */ +QString Output::model() const +{ + Q_D(const Output); + return d->model; +} + +/*! + * \property Output::serialNumber + * \brief The serial number of the screen. + * + * This property holds the serial number of the screen. + */ +QString Output::serialNumber() const +{ + Q_D(const Output); + return d->serialNumber; +} + +/*! + * \property Output::physicalSize + * \brief The physical size of the screen in millimiters. + * + * This property holds the physical size of the screen in millimiters. + */ +QSize Output::physicalSize() const +{ + Q_D(const Output); + return d->physicalSize; +} + +/*! + * \property Output::enabled + * \brief Weather the output is enable or not. + * + * This property holds weather the output is enabled or not. + */ +bool Output::isEnabled() const +{ + Q_D(const Output); + return d->enabled; +} + +/*! + * \property Output::globalPosition + * \brief Position in the global compositor space. + * + * This property holds the output position within the global compositor space. + */ +QPoint Output::globalPosition() const +{ + Q_D(const Output); + return d->globalPosition; +} + +/*! + * \property Output::pixelSize + * \brief Size. + * + * This property holds the output size in pixels, taking transform into account. + * + * \sa Output::modeSize + * \sa Output::transform + */ +QSize Output::pixelSize() const +{ + Q_D(const Output); + + switch (d->transform) { + case Output::Transform::Rotated90: + case Output::Transform::Rotated270: + case Output::Transform::Flipped90: + case Output::Transform::Flipped270: + return modeSize().transposed(); + default: + break; + } + + return modeSize(); +} + +/*! + * \property Output::modeSize + * \brief Actual resolution. + * + * This property holds the actual resolution of the output, without + * being multiplied by the scale. + * + * \sa Output::pixelSize + * \sa Output::scale + */ +QSize Output::modeSize() const +{ + Q_D(const Output); + + if (d->currentMode == d->modes.end()) + return QSize(); + return d->currentMode->size; +} + +/*! + * \property Output::scale + * \brief Scale. + * + * This property holds the output scale. + */ +qreal Output::scale() const +{ + Q_D(const Output); + return d->scale; +} + +/*! + * \property Output::geometry + * \brief Geometry of the output. + * + * This property holds the position of the output in the compositor space + * and the size in pixels. + * + * The geometry is transformed according to the output transform. + * + * \sa Output::transform + * \sa Output::globalPosition + * \sa Output::pixelSize + * \sa Output::scale + */ +QRect Output::geometry() const +{ + Q_D(const Output); + + if (d->currentMode == d->modes.end()) + return QRect(); + + auto rect = QRect(d->globalPosition, pixelSize() / d->scale); + auto x = rect.x(); + auto y = rect.y(); + auto width = rect.width(); + auto height = rect.height(); + + switch (d->transform) { + case Output::Transform::Normal: + return rect; + case Output::Transform::Rotated90: + return QRect(y, rect.left(), height, width); + case Output::Transform::Rotated180: + return QRect(rect.topLeft(), QSize(width, height)); + case Output::Transform::Rotated270: + return QRect(rect.top(), x, height, width); + case Output::Transform::Flipped: + return QRect(x + width, y, -width, height); + case Output::Transform::Flipped90: + return QRect(y + height, rect.left(), -height, width); + case Output::Transform::Flipped180: + return QRect(rect.bottomRight(), QSize(-width, -height)); + case Output::Transform::Flipped270: + return QRect(rect.top(), x + width, height, -width); + } +} + +/*! + * \property Output::refreshRate + * \brief The refresh rate of the output in mHz. + * + * This property holds the refresh rate of the output in mHz. + */ +int Output::refreshRate() const +{ + Q_D(const Output); + + if (d->currentMode == d->modes.end()) + return 0; + return d->currentMode->refreshRate; +} + +/*! + * \property Output::depth + * \brief Color depth. + * + * This property holds the color depth of the output. + * It must be compatible with the image format: for example if the + * Output::format property is QImage::Format_RGB32, depth must be 32. + * + * \ sa Output::format + */ +int Output::depth() const +{ + Q_D(const Output); + return d->depth; +} + +/*! + * \property Output::format + * \brief Image format. + * + * This property holds the image format of the output. + * It must be compatible with color depth: for example if the + * Output::depth property is 32, format might be QImage::Format_RGB32. + * + * \sa Output::depth + */ +QImage::Format Output::format() const +{ + Q_D(const Output); + return d->format; +} + +/*! + * \property Output::powerState + * \brief The power state. + * + * This property holds the power state of the screen. + */ +Output::PowerState Output::powerState() const +{ + Q_D(const Output); + return d->powerState; +} + +void Output::setPowerState(Output::PowerState powerState) +{ + Q_D(Output); + + if (powerState == d->powerState) + return; + + d->powerState = powerState; + emit powerStateChanged(powerState); +} + +Output::Subpixel Output::subpixel() const +{ + Q_D(const Output); + return d->subpixel; +} + +Output::Transform Output::transform() const +{ + Q_D(const Output); + return d->transform; +} + +Output::ContentType Output::contentType() const +{ + Q_D(const Output); + return d->contentType; +} + +void Output::setContentType(Output::ContentType contentType) +{ + Q_D(Output); + + if (d->contentType == contentType) + return; + + d->contentType = contentType; + emit contentTypeChanged(contentType); +} + +QDebug operator<<(QDebug debug, const Output *output) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + + if (output) { + debug << output->metaObject()->className() << '(' << static_cast(output); + debug << ", name=" << output->name(); + debug << ", geometry=" << output->geometry(); + // scale + if (debug.verbosity() > 2) { + debug << ", manufacturer=" << output->manufacturer(); + debug << ", model=" << output->model(); + debug << ", serialNumber=" << output->serialNumber(); + } + debug << ')'; + } else { + debug << "Output(0x0)"; + } + + return debug; +} + +bool Output::Mode::operator==(const Output::Mode &m) const +{ + return flags == m.flags && size == m.size && refreshRate == m.refreshRate; +} + +/* + * OutputPrivate + */ + +OutputPrivate::OutputPrivate(Output *self) + : q_ptr(self) +{ +} + +void OutputPrivate::setManufacturer(const QString &manufacturer) +{ + Q_Q(Output); + + if (this->manufacturer != manufacturer) { + this->manufacturer = manufacturer; + emit q->manufacturerChanged(manufacturer); + } +} + +void OutputPrivate::setModel(const QString &model) +{ + Q_Q(Output); + + if (this->model != model) { + this->model = model; + emit q->modelChanged(model); + } +} + +void OutputPrivate::setSubpixel(Output::Subpixel subpixel) +{ + Q_Q(Output); + + if (this->subpixel != subpixel) { + this->subpixel = subpixel; + emit q->subpixelChanged(subpixel); + } +} + +void OutputPrivate::setTransform(Output::Transform transform) +{ + Q_Q(Output); + + if (this->transform != transform) { + this->transform = transform; + emit q->transformChanged(transform); + } +} + +void OutputPrivate::setPhysicalSize(const QSize &physicalSize) +{ + Q_Q(Output); + + if (this->physicalSize != physicalSize) { + this->physicalSize = physicalSize; + emit q->physicalSizeChanged(physicalSize); + } +} + +void OutputPrivate::setGlobalPosition(const QPoint &globalPosition) +{ + Q_Q(Output); + + if (this->globalPosition != globalPosition) { + this->globalPosition = globalPosition; + emit q->globalPositionChanged(globalPosition); + } +} + +void OutputPrivate::setScale(qreal scale) +{ + Q_Q(Output); + + if (this->scale != scale) { + this->scale = scale; + emit q->scaleChanged(scale); + } +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/output.h b/src/platform/output.h new file mode 100644 index 00000000..85b98194 --- /dev/null +++ b/src/platform/output.h @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class OutputPrivate; + +class LIRIAURORAPLATFORM_EXPORT Output : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid uuid READ uuid CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString manufacturer READ manufacturer NOTIFY manufacturerChanged) + Q_PROPERTY(QString model READ model NOTIFY modelChanged) + Q_PROPERTY(QString serialNumber READ serialNumber CONSTANT) + Q_PROPERTY(QSize physicalSize READ physicalSize NOTIFY physicalSizeChanged) + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) + Q_PROPERTY(QPoint globalPosition READ globalPosition NOTIFY globalPositionChanged) + Q_PROPERTY(QSize modeSize READ modeSize NOTIFY modeSizeChanged) + Q_PROPERTY(QSize pixelSize READ pixelSize NOTIFY pixelSizeChanged) + Q_PROPERTY(qreal scale READ scale NOTIFY scaleChanged) + Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) + Q_PROPERTY(int refreshRate READ refreshRate NOTIFY refreshRateChanged) + Q_PROPERTY(int depth READ depth CONSTANT) + Q_PROPERTY(int format READ format CONSTANT) + Q_PROPERTY(PowerState powerState READ powerState WRITE setPowerState NOTIFY powerStateChanged) + Q_PROPERTY(Subpixel subpixel READ subpixel NOTIFY subpixelChanged) + Q_PROPERTY(Transform transform READ transform NOTIFY transformChanged) + Q_PROPERTY( + ContentType contenType READ contentType WRITE setContentType NOTIFY contentTypeChanged) + Q_DECLARE_PRIVATE(Output) +public: + Q_DISABLE_COPY_MOVE(Output) + + enum class Capability : uint { + PowerState = 1, + Overscan = 1 << 1, + Vrr = 1 << 2, + RgbRange = 1 << 3, + HighDynamicRange = 1 << 4, + WideColorGamut = 1 << 5, + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + enum class PowerState { + On, + Standby, + Suspend, + Off, + }; + Q_ENUM(PowerState) + + enum class Subpixel { + Unknown, + None, + HorizontalRGB, + HorizontalBGR, + VerticalRGB, + VerticalBGR, + }; + Q_ENUM(Subpixel) + + enum class Transform { + Normal, + Rotated90, + Rotated180, + Rotated270, + Flipped, + Flipped90, + Flipped180, + Flipped270, + }; + Q_ENUM(Transform); + + enum class ContentType { + Unknown, + Photo, + Video, + Game, + }; + Q_ENUM(ContentType) + + struct Mode + { + enum class Flag { + None = 0, + Current = 1 << 0, + Preferred = 1 << 1, + }; + Q_DECLARE_FLAGS(Flags, Flag) + + /*! Weather this mode is current or preferred */ + Flags flags = Flag::None; + + /*! Size in pixel space */ + QSize size; + + /*! Refresh rate in mHz */ + int refreshRate = 0; + + bool operator==(const Mode &m) const; + }; + + explicit Output(QObject *parent = nullptr); + ~Output(); + + QUuid uuid() const; + + virtual QString name() const; + virtual QString description() const; + + QString manufacturer() const; + QString model() const; + QString serialNumber() const; + + QSize physicalSize() const; + + bool isEnabled() const; + + QPoint globalPosition() const; + QSize pixelSize() const; + QSize modeSize() const; + + qreal scale() const; + + QRect geometry() const; + + int refreshRate() const; + + int depth() const; + QImage::Format format() const; + + PowerState powerState() const; + void setPowerState(PowerState powerState); + + Subpixel subpixel() const; + + Transform transform() const; + + ContentType contentType() const; + void setContentType(ContentType contentType); + +signals: + void manufacturerChanged(const QString &manufacturer); + void modelChanged(const QString &model); + void physicalSizeChanged(const QSize &physicalSize); + void enabledChanged(bool enabled); + void globalPositionChanged(const QPoint &globalPosition); + void modeSizeChanged(const QSize &modeSize); + void pixelSizeChanged(const QSize &pixelSize); + void scaleChanged(qreal scale); + void geometryChanged(const QRect &geometry); + void refreshRateChanged(int refreshRate); + void powerStateChanged(Output::PowerState powerState); + void subpixelChanged(Output::Subpixel subpixel); + void transformChanged(Output::Transform transform); + void contentTypeChanged(Output::ContentType contentType); + void modeAdded(const Mode &mode); + void modeChanged(const Mode &mode); + +protected: + QScopedPointer const d_ptr; +}; + +LIRIAURORAPLATFORM_EXPORT QDebug operator<<(QDebug debug, const Output *output); + +typedef QList Outputs; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Output::Capabilities) +Q_DECLARE_OPERATORS_FOR_FLAGS(Output::Mode::Flags) + +} // namespace Platform + +} // namespace Aurora + +Q_DECLARE_METATYPE(Aurora::Platform::Output::PowerState) +Q_DECLARE_METATYPE(Aurora::Platform::Output::Subpixel) +Q_DECLARE_METATYPE(Aurora::Platform::Output::Transform) +Q_DECLARE_METATYPE(Aurora::Platform::Output::ContentType) +Q_DECLARE_METATYPE(Aurora::Platform::Output::Mode) diff --git a/src/platform/output_p.h b/src/platform/output_p.h new file mode 100644 index 00000000..8c61d6b5 --- /dev/null +++ b/src/platform/output_p.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Aurora API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT OutputPrivate +{ + Q_DECLARE_PUBLIC(Output) +public: + explicit OutputPrivate(Output *self); + + static OutputPrivate *get(Output *output) + { + return output->d_func(); + } + + void setManufacturer(const QString &manufacturer); + void setModel(const QString &model); + void setSubpixel(Output::Subpixel subpixel); + void setTransform(Output::Transform transform); + void setPhysicalSize(const QSize &physicalSize); + void setGlobalPosition(const QPoint &globalPosition); + void setScale(qreal scale); + + QUuid uuid; + QString manufacturer; + QString model; + QString serialNumber; + QSize physicalSize; + bool enabled = true; + QPoint globalPosition; + qreal scale = 1.0f; + int depth = 0; + QImage::Format format = QImage::Format_Invalid; + Output::PowerState powerState = Output::PowerState::On; + Output::Subpixel subpixel = Output::Subpixel::Unknown; + Output::Transform transform = Output::Transform::Normal; + Output::ContentType contentType = Output::ContentType::Unknown; + QList modes; + QList::iterator currentMode = modes.end(); + +protected: + Output *q_ptr = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/pointerdevice.cpp b/src/platform/pointerdevice.cpp new file mode 100644 index 00000000..549a2fd2 --- /dev/null +++ b/src/platform/pointerdevice.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "pointerdevice.h" + +namespace Aurora { + +namespace Platform { + +PointerDevice::PointerDevice(QObject *parent) + : InputDevice(parent) +{ +} + +InputDevice::DeviceType PointerDevice::deviceType() +{ + return DeviceType::Pointer; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/pointerdevice.h b/src/platform/pointerdevice.h new file mode 100644 index 00000000..076b00a4 --- /dev/null +++ b/src/platform/pointerdevice.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT PointerDevice : public InputDevice +{ + Q_OBJECT +public: + explicit PointerDevice(QObject *parent = nullptr); + + DeviceType deviceType() override; + +signals: + void motion(const QPointF &absPosition); + void buttonPressed(Qt::MouseButton button); + void buttonReleased(Qt::MouseButton button); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/session.cpp b/src/platform/session.cpp new file mode 100644 index 00000000..a4ecc4b5 --- /dev/null +++ b/src/platform/session.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "session.h" +#include "session_noop_p.h" + +namespace Aurora { + +namespace Platform { + +/*! + \class Session + \inmodule AuroraCore + \brief The Session class represents the session controlled by the compositor. + + The Session class provides information about the virtual terminal where the compositor + is running and a way to open files that require special privileges, e.g. DRM devices or + input devices. + */ + +/*! + \enum Session::Type + + This enum type is used to specify the type of the session. + */ + +/*! + \enum Session::Capability + + This enum type is used to specify optional capabilities of the session. + */ + +/*! + \fn Capabilities Session::capabilities() + + Returns the capabilities supported by the session. + */ + +/*! + \fn bool Session::isActive() + + Returns \c true if the session is active; otherwise returns \c false. + */ + +/*! + \fn QString Session::seat() + + Returns the seat name for the Session. + */ + +/*! + \fn uint Session::terminal() + + Returns the terminal controlled by the Session. + */ + +/*! + \fn int Session::openRestricted(const QString &fileName) + + Opens the file with the specified \a fileName. Returns the file descriptor + of the file or \c -1 if an error has occurred. + */ + +/*! + \fn void Session::closeRestricted(int fileDescriptor) + + Closes a file that has been opened using the openRestricted() function. + */ + +/*! + \fn void switchTo(uint terminal) + + Switches to the specified virtual \a terminal. This function does nothing if the + Capability::SwitchTerminal capability is unsupported. + */ + +/*! + \fn void Session::awoke() + + This signal is emitted when the session is resuming from suspend. + */ + +/*! + \fn void Session::activeChanged(bool active) + + This signal is emitted when the active state of the session has changed. + */ + +/*! + \fn void Session::deviceResumed(dev_t deviceId) + + This signal is emitted when the specified device can be used again. + */ + +/*! + \fn void Session::devicePaused(dev_t deviceId) + + This signal is emitted when the given device cannot be used by the compositor + anymore. For example, this normally occurs when switching between VTs. + + Note that when this signal is emitted for a DRM device, master permissions can + be already revoked. + */ + +static const struct +{ + Session::Type type; + std::function()> createFunc; +} s_availableSessions[] = { + { Session::Type::Noop, &NoopSession::create }, +}; + +std::unique_ptr Session::create() +{ + for (const auto &sessionInfo : s_availableSessions) { + std::unique_ptr session = sessionInfo.createFunc(); + if (session) + return session; + } + + return nullptr; +} + +} // namespace Platform + +} // namespace Aurora \ No newline at end of file diff --git a/src/platform/session.h b/src/platform/session.h new file mode 100644 index 00000000..273f956a --- /dev/null +++ b/src/platform/session.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT Session : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) +public: + Q_DISABLE_COPY_MOVE(Session) + + enum class Type { + Noop, + }; + Q_ENUM(Type) + + enum class Capability : uint { + SwitchTerminal = 0x1, + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + virtual Capabilities capabilities() const = 0; + + virtual bool isActive() const = 0; + + virtual QString seat() const = 0; + virtual uint terminal() const = 0; + + virtual int openRestricted(const QString &fileName) = 0; + virtual void closeRestricted(int fileDescriptor) = 0; + + virtual void switchTo(uint terminal) = 0; + + static std::unique_ptr create(); + +signals: + void activeChanged(bool active); + void awoke(); + void deviceResumed(dev_t deviceId); + void devicePaused(dev_t deviceId); + +protected: + explicit Session() = default; +}; + +} // namespace Platform + +} // namespace Aurora + +Q_DECLARE_OPERATORS_FOR_FLAGS(Aurora::Platform::Session::Capabilities) diff --git a/src/platform/session_noop.cpp b/src/platform/session_noop.cpp new file mode 100644 index 00000000..844b93d3 --- /dev/null +++ b/src/platform/session_noop.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2021 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "session_noop_p.h" + +namespace Aurora { + +namespace Platform { + +std::unique_ptr NoopSession::create() +{ + return std::unique_ptr{ new NoopSession() }; +} + +NoopSession::~NoopSession() +{ +} + +NoopSession::Capabilities NoopSession::capabilities() const +{ + return Capabilities(); +} + +bool NoopSession::isActive() const +{ + return true; +} + +QString NoopSession::seat() const +{ + return QStringLiteral("seat0"); +} + +uint NoopSession::terminal() const +{ + return 0; +} + +int NoopSession::openRestricted(const QString &fileName) +{ + return -1; +} + +void NoopSession::closeRestricted(int fileDescriptor) +{ +} + +void NoopSession::switchTo(uint terminal) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/session_noop_p.h b/src/platform/session_noop_p.h new file mode 100644 index 00000000..f45cfe77 --- /dev/null +++ b/src/platform/session_noop_p.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2021 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Aurora API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "session.h" + +namespace Aurora { + +namespace Platform { + +class NoopSession : public Session +{ + Q_OBJECT +public: + static std::unique_ptr create(); + ~NoopSession() override; + + Capabilities capabilities() const override; + + bool isActive() const override; + + QString seat() const override; + uint terminal() const override; + + int openRestricted(const QString &fileName) override; + void closeRestricted(int fileDescriptor) override; + + void switchTo(uint terminal) override; + +private: + explicit NoopSession() = default; +}; + +} // namespace Platform + +} // namespace Aurora \ No newline at end of file diff --git a/src/platform/touchdevice.cpp b/src/platform/touchdevice.cpp new file mode 100644 index 00000000..dbb78c44 --- /dev/null +++ b/src/platform/touchdevice.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "touchdevice.h" + +namespace Aurora { + +namespace Platform { + +TouchDevice::TouchDevice(QObject *parent) + : InputDevice(parent) +{ +} + +InputDevice::DeviceType TouchDevice::deviceType() +{ + return DeviceType::Touch; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/touchdevice.h b/src/platform/touchdevice.h new file mode 100644 index 00000000..4cda8ef7 --- /dev/null +++ b/src/platform/touchdevice.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT TouchDevice : public InputDevice +{ + Q_OBJECT +public: + explicit TouchDevice(QObject *parent = nullptr); + + DeviceType deviceType() override; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/window.cpp b/src/platform/window.cpp new file mode 100644 index 00000000..6ffd73b3 --- /dev/null +++ b/src/platform/window.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "output.h" +#include "window.h" +#include "window_p.h" + +namespace Aurora { + +namespace Platform { + +Window::Window(Output *output, QWindow *qtWindow, QObject *parent) + : QObject(parent) + , d_ptr(new WindowPrivate(this)) +{ + d_ptr->output = output; + d_ptr->qtWindow = qtWindow; +} + +Window::~Window() +{ +} + +Output *Window::output() const +{ + Q_D(const Window); + return d->output; +} + +QWindow *Window::qtWindow() const +{ + Q_D(const Window); + return d->qtWindow; +} + +void *Window::resource(const QByteArray &name) +{ + Q_UNUSED(name) + return nullptr; +} + +void Window::changeCursor(QCursor *cursor) +{ + Q_UNUSED(cursor) +} + +/* + * WindowPrivate + */ + +WindowPrivate::WindowPrivate(Window *self) + : q_ptr(self) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/window.h b/src/platform/window.h new file mode 100644 index 00000000..fb425b17 --- /dev/null +++ b/src/platform/window.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class Output; +class WindowPrivate; + +class LIRIAURORAPLATFORM_EXPORT Window : public QObject +{ + Q_OBJECT + Q_PROPERTY(Output *output READ output CONSTANT) + Q_PROPERTY(QWindow *qtWindow READ qtWindow CONSTANT) + Q_DECLARE_PRIVATE(Window) +public: + ~Window(); + + Output *output() const; + QWindow *qtWindow() const; + + virtual void *resource(const QByteArray &name); + + virtual bool create() = 0; + virtual void destroy() = 0; + + virtual void changeCursor(QCursor *cursor); + +protected: + explicit Window(Output *output, QWindow *qtWindow, QObject *parent = nullptr); + + QScopedPointer const d_ptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/window_p.h b/src/platform/window_p.h new file mode 100644 index 00000000..a140a7c7 --- /dev/null +++ b/src/platform/window_p.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Aurora API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace Aurora { + +namespace Platform { + +class Output; + +class LIRIAURORAPLATFORM_EXPORT WindowPrivate +{ + Q_DECLARE_PUBLIC(Window) +public: + explicit WindowPrivate(Window *self); + + static WindowPrivate *get(Window *window) + { + return window->d_func(); + } + + QPointer output; + QPointer qtWindow; + +protected: + Window *q_ptr = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/xcursor.c b/src/platform/xcursor.c new file mode 100644 index 00000000..9bc5bc70 --- /dev/null +++ b/src/platform/xcursor.c @@ -0,0 +1,551 @@ +/* + * 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. + */ + +#define _DEFAULT_SOURCE +#include "xcursor.h" +#include +#include +#include +#include + +/* + * From libXcursor/include/X11/extensions/Xcursor.h + */ + +#define XcursorTrue 1 +#define XcursorFalse 0 + +/* + * Cursor files start with a header. The header + * contains a magic number, a version number and a + * table of contents which has type and offset information + * for the remaining tables in the file. + * + * File minor versions increment for compatible changes + * File major versions increment for incompatible changes (never, we hope) + * + * Chunks of the same type are always upward compatible. Incompatible + * changes are made with new chunk types; the old data can remain under + * the old type. Upward compatible changes can add header data as the + * header lengths are specified in the file. + * + * File: + * FileHeader + * LISTofChunk + * + * FileHeader: + * CARD32 magic magic number + * CARD32 header bytes in file header + * CARD32 version file version + * CARD32 ntoc number of toc entries + * LISTofFileToc toc table of contents + * + * FileToc: + * CARD32 type entry type + * CARD32 subtype entry subtype (size for images) + * CARD32 position absolute file position + */ + +#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */ + +/* + * Current Xcursor version number. Will be substituted by configure + * from the version in the libXcursor configure.ac file. + */ + +#define XCURSOR_LIB_MAJOR 1 +#define XCURSOR_LIB_MINOR 1 +#define XCURSOR_LIB_REVISION 13 +#define XCURSOR_LIB_VERSION \ + ((XCURSOR_LIB_MAJOR * 10000) + (XCURSOR_LIB_MINOR * 100) + (XCURSOR_LIB_REVISION)) + +/* + * This version number is stored in cursor files; changes to the + * file format require updating this version number + */ +#define XCURSOR_FILE_MAJOR 1 +#define XCURSOR_FILE_MINOR 0 +#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR)) +#define XCURSOR_FILE_HEADER_LEN (4 * 4) +#define XCURSOR_FILE_TOC_LEN (3 * 4) + +typedef struct _XcursorFileToc +{ + XcursorUInt type; /* chunk type */ + XcursorUInt subtype; /* subtype (size for images) */ + XcursorUInt position; /* absolute position in file */ +} XcursorFileToc; + +typedef struct _XcursorFileHeader +{ + XcursorUInt magic; /* magic number */ + XcursorUInt header; /* byte length of header */ + XcursorUInt version; /* file version number */ + XcursorUInt ntoc; /* number of toc entries */ + XcursorFileToc *tocs; /* table of contents */ +} XcursorFileHeader; + +/* + * The rest of the file is a list of chunks, each tagged by type + * and version. + * + * Chunk: + * ChunkHeader + * + * + * + * ChunkHeader: + * CARD32 header bytes in chunk header + type header + * CARD32 type chunk type + * CARD32 subtype chunk subtype + * CARD32 version chunk type version + */ + +#define XCURSOR_CHUNK_HEADER_LEN (4 * 4) + +typedef struct _XcursorChunkHeader +{ + XcursorUInt header; /* bytes in chunk header */ + XcursorUInt type; /* chunk type */ + XcursorUInt subtype; /* chunk subtype (size for images) */ + XcursorUInt version; /* version of this type */ +} XcursorChunkHeader; + +/* + * Here's a list of the known chunk types + */ + +/* + * Comments consist of a 4-byte length field followed by + * UTF-8 encoded text + * + * Comment: + * ChunkHeader header chunk header + * CARD32 length bytes in text + * LISTofCARD8 text UTF-8 encoded text + */ + +#define XCURSOR_COMMENT_TYPE 0xfffe0001 +#define XCURSOR_COMMENT_VERSION 1 +#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 * 4)) +#define XCURSOR_COMMENT_COPYRIGHT 1 +#define XCURSOR_COMMENT_LICENSE 2 +#define XCURSOR_COMMENT_OTHER 3 +#define XCURSOR_COMMENT_MAX_LEN 0x100000 + +typedef struct _XcursorComment +{ + XcursorUInt version; + XcursorUInt comment_type; + char *comment; +} XcursorComment; + +/* + * Each cursor image occupies a separate image chunk. + * The length of the image header follows the chunk header + * so that future versions can extend the header without + * breaking older applications + * + * Image: + * ChunkHeader header chunk header + * CARD32 width actual width + * CARD32 height actual height + * CARD32 xhot hot spot x + * CARD32 yhot hot spot y + * CARD32 delay animation delay + * LISTofCARD32 pixels ARGB pixels + */ + +#define XCURSOR_IMAGE_TYPE 0xfffd0002 +#define XCURSOR_IMAGE_VERSION 1 +#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5 * 4)) +#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */ + +typedef struct _XcursorFile XcursorFile; + +struct _XcursorFile +{ + void *closure; + int (*read)(XcursorFile *file, unsigned char *buf, int len); + int (*write)(XcursorFile *file, unsigned char *buf, int len); + int (*seek)(XcursorFile *file, long offset, int whence); +}; + +typedef struct _XcursorComments +{ + int ncomment; /* number of comments */ + XcursorComment **comments; /* array of XcursorComment pointers */ +} XcursorComments; + +/* + * From libXcursor/src/file.c + */ + +static XcursorImage *XcursorImageCreate(int width, int height) +{ + XcursorImage *image; + + if (width < 0 || height < 0) + return NULL; + if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + + image = malloc(sizeof(XcursorImage) + width * height * sizeof(XcursorPixel)); + if (!image) + return NULL; + image->version = XCURSOR_IMAGE_VERSION; + image->pixels = (XcursorPixel *)(image + 1); + image->size = width > height ? width : height; + image->width = width; + image->height = height; + image->delay = 0; + return image; +} + +static void XcursorImageDestroy(XcursorImage *image) +{ + free(image); +} + +static XcursorImages *XcursorImagesCreate(int size) +{ + XcursorImages *images; + + images = malloc(sizeof(XcursorImages) + size * sizeof(XcursorImage *)); + if (!images) + return NULL; + images->nimage = 0; + images->images = (XcursorImage **)(images + 1); + return images; +} + +void XcursorImagesDestroy(XcursorImages *images) +{ + int n; + + if (!images) + return; + + for (n = 0; n < images->nimage; n++) + XcursorImageDestroy(images->images[n]); + free(images); +} + +static XcursorBool _XcursorReadUInt(XcursorFile *file, XcursorUInt *u) +{ + unsigned char bytes[4]; + + if (!file || !u) + return XcursorFalse; + + if ((*file->read)(file, bytes, 4) != 4) + return XcursorFalse; + + *u = ((XcursorUInt)(bytes[0]) << 0) | ((XcursorUInt)(bytes[1]) << 8) + | ((XcursorUInt)(bytes[2]) << 16) | ((XcursorUInt)(bytes[3]) << 24); + return XcursorTrue; +} + +static void _XcursorFileHeaderDestroy(XcursorFileHeader *fileHeader) +{ + free(fileHeader); +} + +static XcursorFileHeader *_XcursorFileHeaderCreate(XcursorUInt ntoc) +{ + XcursorFileHeader *fileHeader; + + if (ntoc > 0x10000) + return NULL; + fileHeader = malloc(sizeof(XcursorFileHeader) + ntoc * sizeof(XcursorFileToc)); + if (!fileHeader) + return NULL; + fileHeader->magic = XCURSOR_MAGIC; + fileHeader->header = XCURSOR_FILE_HEADER_LEN; + fileHeader->version = XCURSOR_FILE_VERSION; + fileHeader->ntoc = ntoc; + fileHeader->tocs = (XcursorFileToc *)(fileHeader + 1); + return fileHeader; +} + +static XcursorFileHeader *_XcursorReadFileHeader(XcursorFile *file) +{ + XcursorFileHeader head, *fileHeader; + XcursorUInt skip; + unsigned int n; + + if (!file) + return NULL; + + if (!_XcursorReadUInt(file, &head.magic)) + return NULL; + if (head.magic != XCURSOR_MAGIC) + return NULL; + if (!_XcursorReadUInt(file, &head.header)) + return NULL; + if (!_XcursorReadUInt(file, &head.version)) + return NULL; + if (!_XcursorReadUInt(file, &head.ntoc)) + return NULL; + skip = head.header - XCURSOR_FILE_HEADER_LEN; + if (skip) + if ((*file->seek)(file, skip, SEEK_CUR) == EOF) + return NULL; + fileHeader = _XcursorFileHeaderCreate(head.ntoc); + if (!fileHeader) + return NULL; + fileHeader->magic = head.magic; + fileHeader->header = head.header; + fileHeader->version = head.version; + fileHeader->ntoc = head.ntoc; + for (n = 0; n < fileHeader->ntoc; n++) { + if (!_XcursorReadUInt(file, &fileHeader->tocs[n].type)) + break; + if (!_XcursorReadUInt(file, &fileHeader->tocs[n].subtype)) + break; + if (!_XcursorReadUInt(file, &fileHeader->tocs[n].position)) + break; + } + if (n != fileHeader->ntoc) { + _XcursorFileHeaderDestroy(fileHeader); + return NULL; + } + return fileHeader; +} + +static XcursorBool _XcursorSeekToToc(XcursorFile *file, XcursorFileHeader *fileHeader, int toc) +{ + if (!file || !fileHeader + || (*file->seek)(file, fileHeader->tocs[toc].position, SEEK_SET) == EOF) + return XcursorFalse; + return XcursorTrue; +} + +static XcursorBool _XcursorFileReadChunkHeader(XcursorFile *file, XcursorFileHeader *fileHeader, + int toc, XcursorChunkHeader *chunkHeader) +{ + if (!file || !fileHeader || !chunkHeader) + return XcursorFalse; + if (!_XcursorSeekToToc(file, fileHeader, toc)) + return XcursorFalse; + if (!_XcursorReadUInt(file, &chunkHeader->header)) + return XcursorFalse; + if (!_XcursorReadUInt(file, &chunkHeader->type)) + return XcursorFalse; + if (!_XcursorReadUInt(file, &chunkHeader->subtype)) + return XcursorFalse; + if (!_XcursorReadUInt(file, &chunkHeader->version)) + return XcursorFalse; + /* sanity check */ + if (chunkHeader->type != fileHeader->tocs[toc].type + || chunkHeader->subtype != fileHeader->tocs[toc].subtype) + return XcursorFalse; + return XcursorTrue; +} + +#define dist(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) + +static XcursorDim _XcursorFindBestSize(XcursorFileHeader *fileHeader, XcursorDim size, int *nsizesp) +{ + unsigned int n; + int nsizes = 0; + XcursorDim bestSize = 0; + XcursorDim thisSize; + + if (!fileHeader || !nsizesp) + return 0; + + for (n = 0; n < fileHeader->ntoc; n++) { + if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE) + continue; + thisSize = fileHeader->tocs[n].subtype; + if (!bestSize || dist(thisSize, size) < dist(bestSize, size)) { + bestSize = thisSize; + nsizes = 1; + } else if (thisSize == bestSize) + nsizes++; + } + *nsizesp = nsizes; + return bestSize; +} + +static int _XcursorFindImageToc(XcursorFileHeader *fileHeader, XcursorDim size, int count) +{ + unsigned int toc; + XcursorDim thisSize; + + if (!fileHeader) + return 0; + + for (toc = 0; toc < fileHeader->ntoc; toc++) { + if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE) + continue; + thisSize = fileHeader->tocs[toc].subtype; + if (thisSize != size) + continue; + if (!count) + break; + count--; + } + if (toc == fileHeader->ntoc) + return -1; + return toc; +} + +static XcursorImage *_XcursorReadImage(XcursorFile *file, XcursorFileHeader *fileHeader, int toc) +{ + XcursorChunkHeader chunkHeader; + XcursorImage head; + XcursorImage *image; + int n; + XcursorPixel *p; + + if (!file || !fileHeader) + return NULL; + + if (!_XcursorFileReadChunkHeader(file, fileHeader, toc, &chunkHeader)) + return NULL; + if (!_XcursorReadUInt(file, &head.width)) + return NULL; + if (!_XcursorReadUInt(file, &head.height)) + return NULL; + if (!_XcursorReadUInt(file, &head.xhot)) + return NULL; + if (!_XcursorReadUInt(file, &head.yhot)) + return NULL; + if (!_XcursorReadUInt(file, &head.delay)) + return NULL; + /* sanity check data */ + if (head.width > XCURSOR_IMAGE_MAX_SIZE || head.height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + if (head.width == 0 || head.height == 0) + return NULL; + if (head.xhot > head.width || head.yhot > head.height) + return NULL; + + /* Create the image and initialize it */ + image = XcursorImageCreate(head.width, head.height); + if (image == NULL) + return NULL; + if (chunkHeader.version < image->version) + image->version = chunkHeader.version; + image->size = chunkHeader.subtype; + image->xhot = head.xhot; + image->yhot = head.yhot; + image->delay = head.delay; + n = image->width * image->height; + p = image->pixels; + while (n--) { + if (!_XcursorReadUInt(file, p)) { + XcursorImageDestroy(image); + return NULL; + } + p++; + } + return image; +} + +static XcursorImages *XcursorXcFileLoadImages(XcursorFile *file, int size) +{ + XcursorFileHeader *fileHeader; + XcursorDim bestSize; + int nsize; + XcursorImages *images; + int n; + int toc; + + if (!file || size < 0) + return NULL; + fileHeader = _XcursorReadFileHeader(file); + if (!fileHeader) + return NULL; + bestSize = _XcursorFindBestSize(fileHeader, (XcursorDim)size, &nsize); + if (!bestSize) { + _XcursorFileHeaderDestroy(fileHeader); + return NULL; + } + images = XcursorImagesCreate(nsize); + if (!images) { + _XcursorFileHeaderDestroy(fileHeader); + return NULL; + } + for (n = 0; n < nsize; n++) { + toc = _XcursorFindImageToc(fileHeader, bestSize, n); + if (toc < 0) + break; + images->images[images->nimage] = _XcursorReadImage(file, fileHeader, toc); + if (!images->images[images->nimage]) + break; + images->nimage++; + } + _XcursorFileHeaderDestroy(fileHeader); + if (images->nimage != nsize) { + XcursorImagesDestroy(images); + images = NULL; + } + return images; +} + +static int _XcursorStdioFileRead(XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return fread(buf, 1, len, f); +} + +static int _XcursorStdioFileWrite(XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return fwrite(buf, 1, len, f); +} + +static int _XcursorStdioFileSeek(XcursorFile *file, long offset, int whence) +{ + FILE *f = file->closure; + return fseek(f, offset, whence); +} + +static void _XcursorStdioFileInitialize(FILE *stdfile, XcursorFile *file) +{ + file->closure = stdfile; + file->read = _XcursorStdioFileRead; + file->write = _XcursorStdioFileWrite; + file->seek = _XcursorStdioFileSeek; +} + +XcursorImages *XcursorFileLoadImages(const char *file, int size) +{ + XcursorFile f; + XcursorImages *images; + + FILE *fp = fopen(file, "r"); + if (!fp) + return NULL; + + _XcursorStdioFileInitialize(fp, &f); + images = XcursorXcFileLoadImages(&f, size); + fclose(fp); + + return images; +} diff --git a/src/platform/xcursor.h b/src/platform/xcursor.h new file mode 100644 index 00000000..b18aa0a6 --- /dev/null +++ b/src/platform/xcursor.h @@ -0,0 +1,71 @@ +/* + * 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 + +#include + +typedef int XcursorBool; +typedef uint32_t 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 */ +} XcursorImages; + +__attribute__((visibility("default"))) XcursorImages *XcursorFileLoadImages(const char *file, + int size); + +void XcursorImagesDestroy(XcursorImages *images); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/platform/xcursortheme.cpp b/src/platform/xcursortheme.cpp new file mode 100644 index 00000000..72682610 --- /dev/null +++ b/src/platform/xcursortheme.cpp @@ -0,0 +1,365 @@ +// SPDX-FileCopyrightText: 2021-2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include + +#include "xcursor.h" +#include "xcursortheme.h" + +static constexpr struct ShapeMap +{ + Qt::CursorShape shape; + const char name[50]; +} cursorShapeMap[] = { + { Qt::ArrowCursor, "left_ptr" }, + { Qt::ArrowCursor, "default" }, + { Qt::ArrowCursor, "top_left_arrow" }, + { Qt::ArrowCursor, "left_arrow" }, + + { Qt::UpArrowCursor, "up_arrow" }, + + { Qt::CrossCursor, "cross" }, + + { Qt::WaitCursor, "wait" }, + { Qt::WaitCursor, "watch" }, + { Qt::WaitCursor, "0426c94ea35c87780ff01dc239897213" }, + + { Qt::IBeamCursor, "ibeam" }, + { Qt::IBeamCursor, "text" }, + { Qt::IBeamCursor, "xterm" }, + + { Qt::SizeVerCursor, "size_ver" }, + { Qt::SizeVerCursor, "ns-resize" }, + { Qt::SizeVerCursor, "v_double_arrow" }, + { Qt::SizeVerCursor, "00008160000006810000408080010102" }, + + { Qt::SizeHorCursor, "size_hor" }, + { Qt::SizeHorCursor, "ew-resize" }, + { Qt::SizeHorCursor, "h_double_arrow" }, + { Qt::SizeHorCursor, "028006030e0e7ebffc7f7070c0600140" }, + + { Qt::SizeBDiagCursor, "size_bdiag" }, + { Qt::SizeBDiagCursor, "nesw-resize" }, + { Qt::SizeBDiagCursor, "50585d75b494802d0151028115016902" }, + { Qt::SizeBDiagCursor, "fcf1c3c7cd4491d801f1e1c78f100000" }, + + { Qt::SizeFDiagCursor, "size_fdiag" }, + { Qt::SizeFDiagCursor, "nwse-resize" }, + { Qt::SizeFDiagCursor, "38c5dff7c7b8962045400281044508d2" }, + { Qt::SizeFDiagCursor, "c7088f0f3e6c8088236ef8e1e3e70000" }, + + { Qt::SizeAllCursor, "size_all" }, + + { Qt::SplitVCursor, "split_v" }, + { Qt::SplitVCursor, "row-resize" }, + { Qt::SplitVCursor, "sb_v_double_arrow" }, + { Qt::SplitVCursor, "2870a09082c103050810ffdffffe0204" }, + { Qt::SplitVCursor, "c07385c7190e701020ff7ffffd08103c" }, + + { Qt::SplitHCursor, "split_h" }, + { Qt::SplitHCursor, "col-resize" }, + { Qt::SplitHCursor, "sb_h_double_arrow" }, + { Qt::SplitHCursor, "043a9f68147c53184671403ffa811cc5" }, + { Qt::SplitHCursor, "14fef782d02440884392942c11205230" }, + + { Qt::PointingHandCursor, "pointing_hand" }, + { Qt::PointingHandCursor, "pointer" }, + { Qt::PointingHandCursor, "hand1" }, + { Qt::PointingHandCursor, "e29285e634086352946a0e7090d73106" }, + + { Qt::ForbiddenCursor, "forbidden" }, + { Qt::ForbiddenCursor, "not-allowed" }, + { Qt::ForbiddenCursor, "crossed_circle" }, + { Qt::ForbiddenCursor, "circle" }, + { Qt::ForbiddenCursor, "03b6e0fcb3499374a867c041f52298f0" }, + + { Qt::WhatsThisCursor, "whats_this" }, + { Qt::WhatsThisCursor, "help" }, + { Qt::WhatsThisCursor, "question_arrow" }, + { Qt::WhatsThisCursor, "5c6cd98b3f3ebcb1f9c7f1c204630408" }, + { Qt::WhatsThisCursor, "d9ce0ab605698f320427677b458ad60b" }, + + { Qt::BusyCursor, "left_ptr_watch" }, + { Qt::BusyCursor, "half-busy" }, + { Qt::BusyCursor, "progress" }, + { Qt::BusyCursor, "00000000000000020006000e7e9ffc3f" }, + { Qt::BusyCursor, "08e8e1c95fe2fc01f976f1e063a24ccd" }, + + { Qt::OpenHandCursor, "openhand" }, + { Qt::OpenHandCursor, "fleur" }, + { Qt::OpenHandCursor, "5aca4d189052212118709018842178c0" }, + { Qt::OpenHandCursor, "9d800788f1b08800ae810202380a0822" }, + + { Qt::ClosedHandCursor, "closedhand" }, + { Qt::ClosedHandCursor, "grabbing" }, + { Qt::ClosedHandCursor, "208530c400c041818281048008011002" }, + + { Qt::DragCopyCursor, "dnd-copy" }, + { Qt::DragCopyCursor, "copy" }, + + { Qt::DragMoveCursor, "dnd-move" }, + { Qt::DragMoveCursor, "move" }, + + { Qt::DragLinkCursor, "dnd-link" }, + { Qt::DragLinkCursor, "link" }, +}; + +namespace Aurora { + +namespace Platform { + +class XcursorSpritePrivate : public QSharedData +{ +public: + QImage data; + QPoint hotSpot; + std::chrono::milliseconds delay; +}; + +class XcursorThemePrivate : public QSharedData +{ +public: + void load(const QString &themeName, int size, qreal devicePixelRatio); + void loadCursors(const QString &packagePath, int size, qreal devicePixelRatio); + + QHash> registry; +}; + +/* + * XcursorSprite + */ + +XcursorSprite::XcursorSprite() + : d(new XcursorSpritePrivate) +{ +} + +XcursorSprite::XcursorSprite(const XcursorSprite &other) + : d(other.d) +{ +} + +XcursorSprite::~XcursorSprite() +{ +} + +XcursorSprite &XcursorSprite::operator=(const XcursorSprite &other) +{ + d = other.d; + return *this; +} + +XcursorSprite::XcursorSprite(const QImage &data, const QPoint &hotSpot, + const std::chrono::milliseconds &delay) + : d(new XcursorSpritePrivate) +{ + d->data = data; + d->hotSpot = hotSpot; + d->delay = delay; +} + +QImage XcursorSprite::data() const +{ + return d->data; +} + +QPoint XcursorSprite::hotSpot() const +{ + return d->hotSpot; +} + +std::chrono::milliseconds XcursorSprite::delay() const +{ + return d->delay; +} + +static QVector loadCursor(const QString &filePath, int desiredSize, + qreal devicePixelRatio) +{ + XcursorImages *images = + XcursorFileLoadImages(QFile::encodeName(filePath), desiredSize * devicePixelRatio); + if (!images) { + return {}; + } + + QVector sprites; + for (int i = 0; i < images->nimage; ++i) { + const XcursorImage *nativeCursorImage = images->images[i]; + const qreal scale = std::max(qreal(1), qreal(nativeCursorImage->size) / desiredSize); + const QPoint hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot); + const std::chrono::milliseconds delay(nativeCursorImage->delay); + + QImage data(nativeCursorImage->width, nativeCursorImage->height, + QImage::Format_ARGB32_Premultiplied); + data.setDevicePixelRatio(scale); + memcpy(data.bits(), nativeCursorImage->pixels, data.sizeInBytes()); + + sprites.append(XcursorSprite(data, hotspot / scale, delay)); + } + + XcursorImagesDestroy(images); + return sprites; +} + +void XcursorThemePrivate::loadCursors(const QString &packagePath, int size, qreal devicePixelRatio) +{ + const QDir dir(packagePath); + QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); + std::partition(entries.begin(), entries.end(), + [](const QFileInfo &fileInfo) { return !fileInfo.isSymLink(); }); + + for (const QFileInfo &entry : std::as_const(entries)) { + const QByteArray shape = QFile::encodeName(entry.fileName()); + if (registry.contains(shape)) { + continue; + } + if (entry.isSymLink()) { + const QFileInfo symLinkInfo(entry.symLinkTarget()); + if (symLinkInfo.absolutePath() == entry.absolutePath()) { + const auto sprites = registry.value(QFile::encodeName(symLinkInfo.fileName())); + if (!sprites.isEmpty()) { + registry.insert(shape, sprites); + continue; + } + } + } + const QVector sprites = + loadCursor(entry.absoluteFilePath(), size, devicePixelRatio); + if (!sprites.isEmpty()) + registry.insert(shape, sprites); + } +} + +static QStringList searchPaths() +{ + static QStringList paths; + + if (paths.isEmpty()) { + if (const QString env = qEnvironmentVariable("XCURSOR_PATH"); !env.isEmpty()) { + paths.append(env.split(QLatin1Char(':'), Qt::SkipEmptyParts)); + } else { + const QString home = QDir::homePath(); + if (!home.isEmpty()) { + paths.append(home + QLatin1String("/.icons")); + } + const QStringList dataDirs = + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + for (const QString &dataDir : dataDirs) { + paths.append(dataDir + QLatin1String("/icons")); + } + } + } + return paths; +} + +void XcursorThemePrivate::load(const QString &themeName, int size, qreal devicePixelRatio) +{ + const QStringList paths = searchPaths(); + + QStack stack; + QSet loaded; + + stack.push(themeName); + + while (!stack.isEmpty()) { + const QString themeName = stack.pop(); + if (loaded.contains(themeName)) { + continue; + } + + QStringList inherits; + + for (const QString &path : paths) { + const QDir dir(path + QLatin1Char('/') + themeName); + if (!dir.exists()) { + continue; + } + loadCursors(dir.filePath(QStringLiteral("cursors")), size, devicePixelRatio); + if (inherits.isEmpty()) { + auto settings = QSettings(dir.filePath(QStringLiteral("index.theme")), + QSettings::IniFormat); + settings.beginGroup(QStringLiteral("Icon Theme")); + inherits + << settings.value(QStringLiteral("Inherits"), QStringList()).toStringList(); + } + } + + loaded.insert(themeName); + for (auto it = inherits.crbegin(); it != inherits.crend(); ++it) { + stack.push(*it); + } + } +} + +/* + * XcursorTheme + */ + +XcursorTheme::XcursorTheme() + : d(new XcursorThemePrivate) +{ +} + +XcursorTheme::XcursorTheme(const QString &themeName, int size, qreal devicePixelRatio) + : d(new XcursorThemePrivate) +{ + d->load(themeName, size, devicePixelRatio); +} + +XcursorTheme::XcursorTheme(const XcursorTheme &other) + : d(other.d) +{ +} + +XcursorTheme::~XcursorTheme() +{ +} + +XcursorTheme &XcursorTheme::operator=(const XcursorTheme &other) +{ + d = other.d; + return *this; +} + +bool XcursorTheme::operator==(const XcursorTheme &other) +{ + return d == other.d; +} + +bool XcursorTheme::operator!=(const XcursorTheme &other) +{ + return !(*this == other); +} + +bool XcursorTheme::isEmpty() const +{ + return d->registry.isEmpty(); +} + +QVector XcursorTheme::shape(const QByteArray &name) const +{ + return d->registry.value(name); +} + +QVector XcursorTheme::shape(Qt::CursorShape cursorShape) const +{ + const auto compareByShape = [](ShapeMap lhs, ShapeMap rhs) { return lhs.shape < rhs.shape; }; + + const auto p = std::equal_range(std::begin(cursorShapeMap), std::end(cursorShapeMap), + ShapeMap{ cursorShape, "" }, compareByShape); + for (auto it = p.first; it != p.second; ++it) + return shape(QByteArray(it->name)); + + return QVector(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/xcursortheme.h b/src/platform/xcursortheme.h new file mode 100644 index 00000000..d8cbf3bc --- /dev/null +++ b/src/platform/xcursortheme.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2021-2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class XcursorSpritePrivate; +class XcursorThemePrivate; + +class LIRIAURORAPLATFORM_EXPORT XcursorSprite +{ +public: + XcursorSprite(); + XcursorSprite(const XcursorSprite &other); + XcursorSprite(const QImage &data, const QPoint &hotSpot, + const std::chrono::milliseconds &delay); + ~XcursorSprite(); + + XcursorSprite &operator=(const XcursorSprite &other); + + QImage data() const; + QPoint hotSpot() const; + std::chrono::milliseconds delay() const; + +private: + QSharedDataPointer d; +}; + +class LIRIAURORAPLATFORM_EXPORT XcursorTheme +{ +public: + XcursorTheme(); + XcursorTheme(const QString &theme, int size, qreal devicePixelRatio); + XcursorTheme(const XcursorTheme &other); + ~XcursorTheme(); + + XcursorTheme &operator=(const XcursorTheme &other); + + bool operator==(const XcursorTheme &other); + bool operator!=(const XcursorTheme &other); + + bool isEmpty() const; + + QVector shape(const QByteArray &name) const; + QVector shape(Qt::CursorShape cursorShape) const; + +private: + QSharedDataPointer d; +}; + +} // namespace Platform + +} // namespace Aurora