From 73691ebe8414d1ee7bf7d198f986c9a65b00e260 Mon Sep 17 00:00:00 2001 From: Pier Luigi Fiorini Date: Thu, 3 Aug 2023 20:17:31 +0200 Subject: [PATCH] Say hello to AuroraPlatform and AuroraCore AuroraPlatform is a platform abstraction library that glues together compositors with EGL device integration and input device managers. Compositors will use the platform abstraction layer to access input and output devices directly. AuroraCore has utilities for compositors and device integration plugins. Closes: #41 --- CMakeLists.txt | 2 + src/core/CMakeLists.txt | 29 ++ src/core/colorspace.cpp | 416 +++++++++++++++++ src/core/colorspace.h | 165 +++++++ src/core/cursorsource.cpp | 28 ++ src/core/cursorsource.h | 32 ++ src/core/shapecursorsource.cpp | 502 +++++++++++++++++++++ src/core/shapecursorsource.h | 43 ++ src/core/xcursor.c | 551 +++++++++++++++++++++++ src/core/xcursor.h | 70 +++ src/core/xcursortheme.cpp | 255 +++++++++++ src/core/xcursortheme_p.h | 74 +++ src/platform/CMakeLists.txt | 44 ++ src/platform/c_ptr_p.h | 28 ++ src/platform/deviceintegration.cpp | 128 ++++++ src/platform/deviceintegration.h | 84 ++++ src/platform/deviceintegration_p.h | 37 ++ src/platform/deviceintegrationplugin.cpp | 89 ++++ src/platform/deviceintegrationplugin.h | 37 ++ src/platform/edid.cpp | 285 ++++++++++++ src/platform/edid.h | 118 +++++ src/platform/eglconfigchooser.cpp | 85 ++++ src/platform/eglconfigchooser_p.h | 21 + src/platform/eglfsdeviceintegration.cpp | 95 ++++ src/platform/eglfsdeviceintegration_p.h | 16 + src/platform/inputdevice.cpp | 17 + src/platform/inputdevice.h | 37 ++ src/platform/inputmanager.cpp | 38 ++ src/platform/inputmanager.h | 43 ++ src/platform/keyboarddevice.cpp | 258 +++++++++++ src/platform/keyboarddevice.h | 74 +++ src/platform/keyboarddevice_p.h | 71 +++ src/platform/output.cpp | 457 +++++++++++++++++++ src/platform/output.h | 267 +++++++++++ src/platform/output_p.h | 58 +++ src/platform/pointerdevice.cpp | 22 + src/platform/pointerdevice.h | 28 ++ src/platform/session.cpp | 132 ++++++ src/platform/session.h | 64 +++ src/platform/session_noop.cpp | 65 +++ src/platform/session_noop_p.h | 52 +++ src/platform/touchdevice.cpp | 22 + src/platform/touchdevice.h | 23 + src/platform/window.cpp | 58 +++ src/platform/window.h | 45 ++ src/platform/window_p.h | 47 ++ 46 files changed, 5112 insertions(+) create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/colorspace.cpp create mode 100644 src/core/colorspace.h create mode 100644 src/core/cursorsource.cpp create mode 100644 src/core/cursorsource.h create mode 100644 src/core/shapecursorsource.cpp create mode 100644 src/core/shapecursorsource.h create mode 100644 src/core/xcursor.c create mode 100644 src/core/xcursor.h create mode 100644 src/core/xcursortheme.cpp create mode 100644 src/core/xcursortheme_p.h create mode 100644 src/platform/CMakeLists.txt create mode 100644 src/platform/c_ptr_p.h create mode 100644 src/platform/deviceintegration.cpp create mode 100644 src/platform/deviceintegration.h create mode 100644 src/platform/deviceintegration_p.h create mode 100644 src/platform/deviceintegrationplugin.cpp create mode 100644 src/platform/deviceintegrationplugin.h create mode 100644 src/platform/edid.cpp create mode 100644 src/platform/edid.h create mode 100644 src/platform/eglconfigchooser.cpp create mode 100644 src/platform/eglconfigchooser_p.h create mode 100644 src/platform/eglfsdeviceintegration.cpp create mode 100644 src/platform/eglfsdeviceintegration_p.h create mode 100644 src/platform/inputdevice.cpp create mode 100644 src/platform/inputdevice.h create mode 100644 src/platform/inputmanager.cpp create mode 100644 src/platform/inputmanager.h create mode 100644 src/platform/keyboarddevice.cpp create mode 100644 src/platform/keyboarddevice.h create mode 100644 src/platform/keyboarddevice_p.h create mode 100644 src/platform/output.cpp create mode 100644 src/platform/output.h create mode 100644 src/platform/output_p.h create mode 100644 src/platform/pointerdevice.cpp create mode 100644 src/platform/pointerdevice.h create mode 100644 src/platform/session.cpp create mode 100644 src/platform/session.h create mode 100644 src/platform/session_noop.cpp create mode 100644 src/platform/session_noop_p.h create mode 100644 src/platform/touchdevice.cpp create mode 100644 src/platform/touchdevice.h create mode 100644 src/platform/window.cpp create mode 100644 src/platform/window.h create mode 100644 src/platform/window_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a5c4249a..d080b66d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,8 @@ add_subdirectory(src/global) if(FEATURE_aurora_xkbcommon) add_subdirectory(src/platformsupport/xkbcommon) endif() +add_subdirectory(src/core) +add_subdirectory(src/platform) add_subdirectory(src/compositor) if(FEATURE_aurora_brcm) add_subdirectory(src/plugins/hardwareintegration/compositor/brcm-egl) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 00000000..21a4c1b2 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +# SPDX-License-Identifier: BSD-3-Clause + +include(ECMQtDeclareLoggingCategory) +ecm_qt_declare_logging_category( + AuroraCore_SOURCES + HEADER "auroracoreloggingcategories.h" + IDENTIFIER "Aurora::Core::gLcAuroraCore" + CATEGORY_NAME "aurora.core" + DEFAULT_SEVERITY "Info" + DESCRIPTION "Aurora core library" +) + +liri_add_module(AuroraCore + DESCRIPTION + "Utility library for Wayland compositors using Aurora" + SOURCES + colorspace.cpp colorspace.h + cursorsource.cpp cursorsource.h + shapecursorsource.cpp shapecursorsource.h + xcursor.c xcursor.h + xcursortheme.cpp xcursortheme_p.h + ${AuroraCore_SOURCES} + PUBLIC_LIBRARIES + Qt6::Core + Qt6::Gui +) + +liri_finalize_module(AuroraCore) diff --git a/src/core/colorspace.cpp b/src/core/colorspace.cpp new file mode 100644 index 00000000..094a276e --- /dev/null +++ b/src/core/colorspace.cpp @@ -0,0 +1,416 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "colorspace.h" + +#include + +namespace Aurora { + +namespace Core { + +static QMatrix3x3 inverse(const QMatrix3x3 &m) +{ + const double determinant = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) + - m(0, 1) * (m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0)) + + m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0)); + QMatrix3x3 ret; + ret(0, 0) = (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) / determinant; + ret(0, 1) = (m(0, 2) * m(2, 1) - m(0, 1) * m(2, 2)) / determinant; + ret(0, 2) = (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1)) / determinant; + ret(1, 0) = (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) / determinant; + ret(1, 1) = (m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0)) / determinant; + ret(1, 2) = (m(1, 0) * m(0, 2) - m(0, 0) * m(1, 2)) / determinant; + ret(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) / determinant; + ret(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) / determinant; + ret(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) / determinant; + return ret; +} + +static QMatrix3x3 matrixFromColumns(const QVector3D &first, const QVector3D &second, + const QVector3D &third) +{ + QMatrix3x3 ret; + ret(0, 0) = first.x(); + ret(1, 0) = first.y(); + ret(2, 0) = first.z(); + ret(0, 1) = second.x(); + ret(1, 1) = second.y(); + ret(2, 1) = second.z(); + ret(0, 2) = third.x(); + ret(1, 2) = third.y(); + ret(2, 2) = third.z(); + return ret; +} + +static QVector3D operator*(const QMatrix3x3 &mat, const QVector3D &v) +{ + return QVector3D(mat(0, 0) * v.x() + mat(0, 1) * v.y() + mat(0, 2) * v.z(), + mat(1, 0) * v.x() + mat(1, 1) * v.y() + mat(1, 2) * v.z(), + mat(2, 0) * v.x() + mat(2, 1) * v.y() + mat(2, 2) * v.z()); +} + +QVector3D Colorimetry::xyToXYZ(QVector2D xy) +{ + return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y()); +} + +QVector2D Colorimetry::xyzToXY(QVector3D xyz) +{ + xyz /= xyz.y(); + return QVector2D(xyz.x() / (xyz.x() + xyz.y() + xyz.z()), + xyz.y() / (xyz.x() + xyz.y() + xyz.z())); +} + +QMatrix3x3 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, + QVector2D destinationWhitepoint) +{ + static const QMatrix3x3 bradford = []() { + QMatrix3x3 ret; + ret(0, 0) = 0.8951; + ret(0, 1) = 0.2664; + ret(0, 2) = -0.1614; + ret(1, 0) = -0.7502; + ret(1, 1) = 1.7135; + ret(1, 2) = 0.0367; + ret(2, 0) = 0.0389; + ret(2, 1) = -0.0685; + ret(2, 2) = 1.0296; + return ret; + }(); + static const QMatrix3x3 inverseBradford = []() { + QMatrix3x3 ret; + ret(0, 0) = 0.9869929; + ret(0, 1) = -0.1470543; + ret(0, 2) = 0.1599627; + ret(1, 0) = 0.4323053; + ret(1, 1) = 0.5183603; + ret(1, 2) = 0.0492912; + ret(2, 0) = -0.0085287; + ret(2, 1) = 0.0400428; + ret(2, 2) = 0.9684867; + return ret; + }(); + if (sourceWhitepoint == destinationWhitepoint) { + return QMatrix3x3{}; + } + const QVector3D factors = + (bradford * xyToXYZ(destinationWhitepoint)) / (bradford * xyToXYZ(sourceWhitepoint)); + QMatrix3x3 adaptation{}; + adaptation(0, 0) = factors.x(); + adaptation(1, 1) = factors.y(); + adaptation(2, 2) = factors.z(); + return inverseBradford * adaptation * bradford; +} + +QMatrix3x3 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, + QVector3D white) +{ + const auto component_scale = inverse(matrixFromColumns(red, green, blue)) * white; + return matrixFromColumns(red * component_scale.x(), green * component_scale.y(), + blue * component_scale.z()); +} + +Colorimetry::Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white) + : m_red(red) + , m_green(green) + , m_blue(blue) + , m_white(white) + , m_toXYZ(calculateToXYZMatrix(xyToXYZ(red), xyToXYZ(green), xyToXYZ(blue), xyToXYZ(white))) + , m_fromXYZ(inverse(m_toXYZ)) +{ +} + +Colorimetry::Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white) + : m_red(xyzToXY(red)) + , m_green(xyzToXY(green)) + , m_blue(xyzToXY(blue)) + , m_white(xyzToXY(white)) + , m_toXYZ(calculateToXYZMatrix(red, green, blue, white)) + , m_fromXYZ(inverse(m_toXYZ)) +{ +} + +const QMatrix3x3 &Colorimetry::toXYZ() const +{ + return m_toXYZ; +} + +const QMatrix3x3 &Colorimetry::fromXYZ() const +{ + return m_fromXYZ; +} + +QMatrix3x3 Colorimetry::toOther(const Colorimetry &other) const +{ + // rendering intent is relative colorimetric, so adapt to the different whitepoint + return other.fromXYZ() * chromaticAdaptationMatrix(this->white(), other.white()) * toXYZ(); +} + +Colorimetry Colorimetry::adaptedTo(QVector2D newWhitepoint) const +{ + const auto mat = chromaticAdaptationMatrix(this->white(), newWhitepoint); + return Colorimetry{ + xyzToXY(mat * xyToXYZ(red())), + xyzToXY(mat * xyToXYZ(green())), + xyzToXY(mat * xyToXYZ(blue())), + newWhitepoint, + }; +} + +bool Colorimetry::operator==(const Colorimetry &other) const +{ + return red() == other.red() && green() == other.green() && blue() == other.blue() + && white() == other.white(); +} + +bool Colorimetry::operator==(NamedColorimetry name) const +{ + return *this == fromName(name); +} + +const QVector2D &Colorimetry::red() const +{ + return m_red; +} + +const QVector2D &Colorimetry::green() const +{ + return m_green; +} + +const QVector2D &Colorimetry::blue() const +{ + return m_blue; +} + +const QVector2D &Colorimetry::white() const +{ + return m_white; +} + +static const Colorimetry BT709 = Colorimetry{ + QVector2D{ 0.64, 0.33 }, + QVector2D{ 0.30, 0.60 }, + QVector2D{ 0.15, 0.06 }, + QVector2D{ 0.3127, 0.3290 }, +}; + +static const Colorimetry BT2020 = Colorimetry{ + QVector2D{ 0.708, 0.292 }, + QVector2D{ 0.170, 0.797 }, + QVector2D{ 0.131, 0.046 }, + QVector2D{ 0.3127, 0.3290 }, +}; + +const Colorimetry &Colorimetry::fromName(NamedColorimetry name) +{ + switch (name) { + case NamedColorimetry::BT709: + return BT709; + case NamedColorimetry::BT2020: + return BT2020; + } + Q_UNREACHABLE(); +} + +const ColorDescription ColorDescription::sRGB = ColorDescription( + NamedColorimetry::BT709, NamedTransferFunction::gamma22, 100, 0, 100, 100, 0); + +static Colorimetry sRGBColorimetry(double factor) +{ + return Colorimetry{ + BT709.red() * (1 - factor) + BT2020.red() * factor, + BT709.green() * (1 - factor) + BT2020.green() * factor, + BT709.blue() * (1 - factor) + BT2020.blue() * factor, + BT709.white(), // whitepoint is the same + }; +} + +ColorDescription::ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, + double sdrBrightness, double minHdrBrightness, + double maxFrameAverageBrightness, + double maxHdrHighlightBrightness, double sdrGamutWideness) + : m_colorimetry(colorimety) + , m_transferFunction(tf) + , m_sdrColorimetry(sRGBColorimetry(sdrGamutWideness)) + , m_sdrGamutWideness(sdrGamutWideness) + , m_sdrBrightness(sdrBrightness) + , m_minHdrBrightness(minHdrBrightness) + , m_maxFrameAverageBrightness(maxFrameAverageBrightness) + , m_maxHdrHighlightBrightness(maxHdrHighlightBrightness) +{ +} + +ColorDescription::ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, + double sdrBrightness, double minHdrBrightness, + double maxFrameAverageBrightness, + double maxHdrHighlightBrightness, double sdrGamutWideness) + : m_colorimetry(Colorimetry::fromName(colorimetry)) + , m_transferFunction(tf) + , m_sdrColorimetry(sRGBColorimetry(sdrGamutWideness)) + , m_sdrGamutWideness(sdrGamutWideness) + , m_sdrBrightness(sdrBrightness) + , m_minHdrBrightness(minHdrBrightness) + , m_maxFrameAverageBrightness(maxFrameAverageBrightness) + , m_maxHdrHighlightBrightness(maxHdrHighlightBrightness) +{ +} + +const Colorimetry &ColorDescription::colorimetry() const +{ + return m_colorimetry; +} + +const Colorimetry &ColorDescription::sdrColorimetry() const +{ + return m_sdrColorimetry; +} + +NamedTransferFunction ColorDescription::transferFunction() const +{ + return m_transferFunction; +} + +double ColorDescription::sdrBrightness() const +{ + return m_sdrBrightness; +} + +double ColorDescription::minHdrBrightness() const +{ + return m_minHdrBrightness; +} + +double ColorDescription::maxFrameAverageBrightness() const +{ + return m_maxFrameAverageBrightness; +} + +double ColorDescription::maxHdrHighlightBrightness() const +{ + return m_maxHdrHighlightBrightness; +} + +double ColorDescription::sdrGamutWideness() const +{ + return m_sdrGamutWideness; +} + +bool ColorDescription::operator==(const ColorDescription &other) const +{ + return m_colorimetry == other.m_colorimetry && m_transferFunction == other.m_transferFunction + && m_sdrGamutWideness == other.m_sdrGamutWideness + && m_sdrBrightness == other.m_sdrBrightness + && m_minHdrBrightness == other.m_minHdrBrightness + && m_maxFrameAverageBrightness == other.m_maxFrameAverageBrightness + && m_maxHdrHighlightBrightness == other.m_maxHdrHighlightBrightness; +} + +static float srgbToLinear(float sRGB) +{ + if (sRGB < 0.04045) { + return std::max(sRGB / 12.92, 0.0); + } else { + return std::clamp(std::pow((sRGB + 0.055) / 1.055, 12.0 / 5.0), 0.0, 1.0); + } +} + +static float linearToSRGB(float linear) +{ + if (linear < 0.0031308) { + return std::max(linear / 12.92, 0.0); + } else { + return std::clamp(std::pow(linear, 5.0 / 12.0) * 1.055 - 0.055, 0.0, 1.0); + } +} + +static float nitsToPQ(float nits) +{ + const float normalized = std::clamp(nits / 10000.0f, 0.0f, 1.0f); + const float c1 = 0.8359375; + const float c2 = 18.8515625; + const float c3 = 18.6875; + const float m1 = 0.1593017578125; + const float m2 = 78.84375; + const float powed = std::pow(normalized, m1); + const float num = c1 + c2 * powed; + const float denum = 1 + c3 * powed; + return std::pow(num / denum, m2); +} + +static float pqToNits(float pq) +{ + const float c1 = 0.8359375; + const float c2 = 18.8515625; + const float c3 = 18.6875; + const float m1_inv = 1.0 / 0.1593017578125; + const float m2_inv = 1.0 / 78.84375; + const float powed = std::pow(pq, m2_inv); + const float num = std::max(powed - c1, 0.0f); + const float den = c2 - c3 * powed; + return 10000.0f * std::pow(num / den, m1_inv); +} + +static QVector3D clamp(const QVector3D &vect, float min = 0, float max = 1) +{ + return QVector3D(std::clamp(vect.x(), min, max), std::clamp(vect.y(), min, max), + std::clamp(vect.z(), min, max)); +} + +QVector3D ColorDescription::encodedToNits(const QVector3D &nits, NamedTransferFunction tf, + double sdrBrightness) +{ + switch (tf) { + case NamedTransferFunction::sRGB: + return sdrBrightness + * QVector3D(srgbToLinear(nits.x()), srgbToLinear(nits.y()), srgbToLinear(nits.z())); + case NamedTransferFunction::gamma22: + return sdrBrightness + * QVector3D(std::pow(nits.x(), 2.2), std::pow(nits.y(), 2.2), + std::pow(nits.z(), 2.2)); + case NamedTransferFunction::linear: + return nits; + case NamedTransferFunction::scRGB: + return nits * 80.0f; + case NamedTransferFunction::PerceptualQuantizer: + return QVector3D(pqToNits(nits.x()), pqToNits(nits.y()), pqToNits(nits.z())); + } + Q_UNREACHABLE(); +} + +QVector3D ColorDescription::nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, + double sdrBrightness) +{ + switch (tf) { + case NamedTransferFunction::sRGB: { + const auto clamped = clamp(rgb / sdrBrightness); + return QVector3D(linearToSRGB(clamped.x()), linearToSRGB(clamped.y()), + linearToSRGB(clamped.z())); + } + case NamedTransferFunction::gamma22: { + const auto clamped = clamp(rgb / sdrBrightness); + return QVector3D(std::pow(clamped.x(), 1 / 2.2), std::pow(clamped.y(), 1 / 2.2), + std::pow(clamped.z(), 1 / 2.2)); + } + case NamedTransferFunction::linear: + return rgb; + case NamedTransferFunction::scRGB: + return rgb / 80.0f; + case NamedTransferFunction::PerceptualQuantizer: + return QVector3D(nitsToPQ(rgb.x()), nitsToPQ(rgb.y()), nitsToPQ(rgb.z())); + } + Q_UNREACHABLE(); +} + +QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) const +{ + rgb = encodedToNits(rgb, m_transferFunction, m_sdrBrightness); + rgb = m_colorimetry.toOther(dst.colorimetry()) * rgb; + return nitsToEncoded(rgb, dst.transferFunction(), dst.sdrBrightness()); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/core/colorspace.h b/src/core/colorspace.h new file mode 100644 index 00000000..bfc2dbb0 --- /dev/null +++ b/src/core/colorspace.h @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include +#include + +#include + +namespace Aurora { + +namespace Core { + +enum class NamedColorimetry { + BT709, + BT2020, +}; + +/** + * Describes the definition of colors in a color space. + * Red, green and blue define the chromaticities ("absolute colors") of the red, green and blue LEDs + * on a display in xy coordinates White defines the the chromaticity of the reference white in xy + * coordinates + */ +class LIRIAURORACORE_EXPORT Colorimetry +{ +public: + static const Colorimetry &fromName(NamedColorimetry name); + /** + * @returns the XYZ representation of the xyY color passed in. Y is assumed to be one + */ + static QVector3D xyToXYZ(QVector2D xy); + /** + * @returns the xyY representation of the XYZ color passed in. Y is normalized to be one + */ + static QVector2D xyzToXY(QVector3D xyz); + /** + * @returns a matrix adapting XYZ values from the source whitepoint to the destination + * whitepoint with the Bradford transform + */ + static QMatrix3x3 chromaticAdaptationMatrix(QVector2D sourceWhitepoint, + QVector2D destinationWhitepoint); + + static QMatrix3x3 calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, + QVector3D white); + + explicit Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white); + explicit Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white); + + /** + * @returns a matrix that transforms from the linear RGB representation of colors in this + * colorimetry to the XYZ representation + */ + const QMatrix3x3 &toXYZ() const; + /** + * @returns a matrix that transforms from the XYZ representation to the linear RGB + * representation of colors in this colorimetry + */ + const QMatrix3x3 &fromXYZ() const; + /** + * @returns a matrix that transforms from linear RGB in this colorimetry to linear RGB in the + * other colorimetry the rendering intent is relative colorimetric + */ + QMatrix3x3 toOther(const Colorimetry &colorimetry) const; + bool operator==(const Colorimetry &other) const; + bool operator==(NamedColorimetry name) const; + /** + * @returns this colorimetry, adapted to the new whitepoint using the Bradford transform + */ + Colorimetry adaptedTo(QVector2D newWhitepoint) const; + + const QVector2D &red() const; + const QVector2D &green() const; + const QVector2D &blue() const; + const QVector2D &white() const; + +private: + QVector2D m_red; + QVector2D m_green; + QVector2D m_blue; + QVector2D m_white; + QMatrix3x3 m_toXYZ; + QMatrix3x3 m_fromXYZ; +}; + +/** + * Describes an EOTF, that is, how encoded brightness values are converted to light + */ +enum class NamedTransferFunction { + sRGB = 0, + linear = 1, + PerceptualQuantizer = 2, + scRGB = 3, + gamma22 = 4, +}; + +/** + * Describes the meaning of encoded color values, with additional metadata for how to convert + * between different encodings Note that not all properties of this description are relevant in all + * contexts + */ +class LIRIAURORACORE_EXPORT ColorDescription +{ +public: + /** + * @param colorimety the colorimety of this description + * @param tf the transfer function of this description + * @param sdrBrightness the brightness of SDR content + * @param minHdrBrightness the minimum brightness of HDR content + * @param maxFrameAverageBrightness the maximum brightness of HDR content, if the whole screen + * is white + * @param maxHdrHighlightBrightness the maximum brightness of HDR content, for a small part of + * the screen only + * @param sdrGamutWideness the gamut wideness of sRGB content; 0 is rec.709, 1 is rec.2020, + * everything in between is interpolated + */ + explicit ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, + double sdrBrightness, double minHdrBrightness, + double maxFrameAverageBrightness, double maxHdrHighlightBrightness, + double sdrGamutWideness); + explicit ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, + double sdrBrightness, double minHdrBrightness, + double maxFrameAverageBrightness, double maxHdrHighlightBrightness, + double sdrGamutWideness); + + const Colorimetry &colorimetry() const; + const Colorimetry &sdrColorimetry() const; + NamedTransferFunction transferFunction() const; + double sdrBrightness() const; + double minHdrBrightness() const; + double maxFrameAverageBrightness() const; + double maxHdrHighlightBrightness() const; + double sdrGamutWideness() const; + + bool operator==(const ColorDescription &other) const; + + QVector3D mapTo(QVector3D rgb, const ColorDescription &other) const; + + /** + * This color description describes display-referred sRGB, with a gamma22 transfer function + */ + static const ColorDescription sRGB; + static QVector3D encodedToNits(const QVector3D &nits, NamedTransferFunction tf, + double sdrBrightness); + static QVector3D nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, + double sdrBrightness); + +private: + Colorimetry m_colorimetry; + NamedTransferFunction m_transferFunction; + Colorimetry m_sdrColorimetry; + double m_sdrGamutWideness; + double m_sdrBrightness; + double m_minHdrBrightness; + double m_maxFrameAverageBrightness; + double m_maxHdrHighlightBrightness; +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/core/cursorsource.cpp b/src/core/cursorsource.cpp new file mode 100644 index 00000000..45e10569 --- /dev/null +++ b/src/core/cursorsource.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2022 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "cursorsource.h" + +namespace Aurora { + +namespace Core { + +CursorSource::CursorSource(QObject *parent) + : QObject(parent) +{ +} + +QSizeF CursorSource::size() const +{ + return QSizeF(0, 0); +} + +QPointF CursorSource::hotSpot() const +{ + return QPointF(0, 0); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/core/cursorsource.h b/src/core/cursorsource.h new file mode 100644 index 00000000..7c8a634b --- /dev/null +++ b/src/core/cursorsource.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2022 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +namespace Aurora { + +namespace Core { + +class LIRIAURORACORE_EXPORT CursorSource : public QObject +{ + Q_OBJECT +public: + explicit CursorSource(QObject *parent = nullptr); + + virtual QSizeF size() const; + virtual QPointF hotSpot() const; + +signals: + void changed(); +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/core/shapecursorsource.cpp b/src/core/shapecursorsource.cpp new file mode 100644 index 00000000..7354df87 --- /dev/null +++ b/src/core/shapecursorsource.cpp @@ -0,0 +1,502 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2022 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "shapecursorsource.h" +#include "xcursortheme_p.h" + +static QByteArray cursorShapeToName(Qt::CursorShape shape) +{ + switch (shape) { + case Qt::ArrowCursor: + return QByteArrayLiteral("left_ptr"); + case Qt::UpArrowCursor: + return QByteArrayLiteral("up_arrow"); + case Qt::CrossCursor: + return QByteArrayLiteral("cross"); + case Qt::WaitCursor: + return QByteArrayLiteral("wait"); + case Qt::IBeamCursor: + return QByteArrayLiteral("ibeam"); + case Qt::SizeVerCursor: + return QByteArrayLiteral("size_ver"); + case Qt::SizeHorCursor: + return QByteArrayLiteral("size_hor"); + case Qt::SizeBDiagCursor: + return QByteArrayLiteral("size_bdiag"); + case Qt::SizeFDiagCursor: + return QByteArrayLiteral("size_fdiag"); + case Qt::SizeAllCursor: + return QByteArrayLiteral("size_all"); + case Qt::SplitVCursor: + return QByteArrayLiteral("split_v"); + case Qt::SplitHCursor: + return QByteArrayLiteral("split_h"); + case Qt::PointingHandCursor: + return QByteArrayLiteral("pointing_hand"); + case Qt::ForbiddenCursor: + return QByteArrayLiteral("forbidden"); + case Qt::OpenHandCursor: + return QByteArrayLiteral("openhand"); + case Qt::ClosedHandCursor: + return QByteArrayLiteral("closedhand"); + case Qt::WhatsThisCursor: + return QByteArrayLiteral("whats_this"); + case Qt::BusyCursor: + return QByteArrayLiteral("left_ptr_watch"); + case Qt::DragMoveCursor: + return QByteArrayLiteral("dnd-move"); + case Qt::DragCopyCursor: + return QByteArrayLiteral("dnd-copy"); + case Qt::DragLinkCursor: + return QByteArrayLiteral("dnd-link"); +#if 0 + case KWin::ExtendedCursor::SizeNorthEast: + return QByteArrayLiteral("ne-resize"); + case KWin::ExtendedCursor::SizeNorth: + return QByteArrayLiteral("n-resize"); + case KWin::ExtendedCursor::SizeNorthWest: + return QByteArrayLiteral("nw-resize"); + case KWin::ExtendedCursor::SizeEast: + return QByteArrayLiteral("e-resize"); + case KWin::ExtendedCursor::SizeWest: + return QByteArrayLiteral("w-resize"); + case KWin::ExtendedCursor::SizeSouthEast: + return QByteArrayLiteral("se-resize"); + case KWin::ExtendedCursor::SizeSouth: + return QByteArrayLiteral("s-resize"); + case KWin::ExtendedCursor::SizeSouthWest: + return QByteArrayLiteral("sw-resize"); +#endif + default: + return QByteArray(); + } +} + +static QByteArrayList cursorAlternativeNames(const QByteArray &name) +{ + static const QHash alternatives = { + { + QByteArrayLiteral("left_ptr"), + { + QByteArrayLiteral("arrow"), + QByteArrayLiteral("dnd-none"), + QByteArrayLiteral("op_left_arrow"), + }, + }, + { + QByteArrayLiteral("cross"), + { + QByteArrayLiteral("crosshair"), + QByteArrayLiteral("diamond-cross"), + QByteArrayLiteral("cross-reverse"), + }, + }, + { + QByteArrayLiteral("up_arrow"), + { + QByteArrayLiteral("center_ptr"), + QByteArrayLiteral("sb_up_arrow"), + QByteArrayLiteral("centre_ptr"), + }, + }, + { + QByteArrayLiteral("wait"), + { + QByteArrayLiteral("watch"), + QByteArrayLiteral("progress"), + }, + }, + { + QByteArrayLiteral("ibeam"), + { + QByteArrayLiteral("xterm"), + QByteArrayLiteral("text"), + }, + }, + { + QByteArrayLiteral("size_all"), + { + QByteArrayLiteral("fleur"), + }, + }, + { + QByteArrayLiteral("pointing_hand"), + { + QByteArrayLiteral("hand2"), + QByteArrayLiteral("hand"), + QByteArrayLiteral("hand1"), + QByteArrayLiteral("pointer"), + QByteArrayLiteral("e29285e634086352946a0e7090d73106"), + QByteArrayLiteral("9d800788f1b08800ae810202380a0822"), + }, + }, + { + QByteArrayLiteral("size_ver"), + { + QByteArrayLiteral("00008160000006810000408080010102"), + QByteArrayLiteral("sb_v_double_arrow"), + QByteArrayLiteral("v_double_arrow"), + QByteArrayLiteral("n-resize"), + QByteArrayLiteral("s-resize"), + QByteArrayLiteral("col-resize"), + QByteArrayLiteral("top_side"), + QByteArrayLiteral("bottom_side"), + QByteArrayLiteral("base_arrow_up"), + QByteArrayLiteral("base_arrow_down"), + QByteArrayLiteral("based_arrow_down"), + QByteArrayLiteral("based_arrow_up"), + }, + }, + { + QByteArrayLiteral("size_hor"), + { + QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"), + QByteArrayLiteral("sb_h_double_arrow"), + QByteArrayLiteral("h_double_arrow"), + QByteArrayLiteral("e-resize"), + QByteArrayLiteral("w-resize"), + QByteArrayLiteral("row-resize"), + QByteArrayLiteral("right_side"), + QByteArrayLiteral("left_side"), + }, + }, + { + QByteArrayLiteral("size_bdiag"), + { + QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"), + QByteArrayLiteral("fd_double_arrow"), + QByteArrayLiteral("bottom_left_corner"), + QByteArrayLiteral("top_right_corner"), + }, + }, + { + QByteArrayLiteral("size_fdiag"), + { + QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"), + QByteArrayLiteral("bd_double_arrow"), + QByteArrayLiteral("bottom_right_corner"), + QByteArrayLiteral("top_left_corner"), + }, + }, + { + QByteArrayLiteral("whats_this"), + { + QByteArrayLiteral("d9ce0ab605698f320427677b458ad60b"), + QByteArrayLiteral("left_ptr_help"), + QByteArrayLiteral("help"), + QByteArrayLiteral("question_arrow"), + QByteArrayLiteral("dnd-ask"), + QByteArrayLiteral("5c6cd98b3f3ebcb1f9c7f1c204630408"), + }, + }, + { + QByteArrayLiteral("split_h"), + { + QByteArrayLiteral("14fef782d02440884392942c11205230"), + QByteArrayLiteral("size_hor"), + }, + }, + { + QByteArrayLiteral("split_v"), + { + QByteArrayLiteral("2870a09082c103050810ffdffffe0204"), + QByteArrayLiteral("size_ver"), + }, + }, + { + QByteArrayLiteral("forbidden"), + { + QByteArrayLiteral("03b6e0fcb3499374a867c041f52298f0"), + QByteArrayLiteral("circle"), + QByteArrayLiteral("dnd-no-drop"), + QByteArrayLiteral("not-allowed"), + }, + }, + { + QByteArrayLiteral("left_ptr_watch"), + { + QByteArrayLiteral("3ecb610c1bf2410f44200f48c40d3599"), + QByteArrayLiteral("00000000000000020006000e7e9ffc3f"), + QByteArrayLiteral("08e8e1c95fe2fc01f976f1e063a24ccd"), + }, + }, + { + QByteArrayLiteral("openhand"), + { + QByteArrayLiteral("9141b49c8149039304290b508d208c40"), + QByteArrayLiteral("all_scroll"), + QByteArrayLiteral("all-scroll"), + }, + }, + { + QByteArrayLiteral("closedhand"), + { + QByteArrayLiteral("05e88622050804100c20044008402080"), + QByteArrayLiteral("4498f0e0c1937ffe01fd06f973665830"), + QByteArrayLiteral("9081237383d90e509aa00f00170e968f"), + QByteArrayLiteral("fcf21c00b30f7e3f83fe0dfd12e71cff"), + }, + }, + { + QByteArrayLiteral("dnd-link"), + { + QByteArrayLiteral("link"), + QByteArrayLiteral("alias"), + QByteArrayLiteral("3085a0e285430894940527032f8b26df"), + QByteArrayLiteral("640fb0e74195791501fd1ed57b41487f"), + QByteArrayLiteral("a2a266d0498c3104214a47bd64ab0fc8"), + }, + }, + { + QByteArrayLiteral("dnd-copy"), + { + QByteArrayLiteral("copy"), + QByteArrayLiteral("1081e37283d90000800003c07f3ef6bf"), + QByteArrayLiteral("6407b0e94181790501fd1e167b474872"), + QByteArrayLiteral("b66166c04f8c3109214a4fbd64a50fc8"), + }, + }, + { + QByteArrayLiteral("dnd-move"), + { + QByteArrayLiteral("move"), + }, + }, + { + QByteArrayLiteral("sw-resize"), + { + QByteArrayLiteral("size_bdiag"), + QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"), + QByteArrayLiteral("fd_double_arrow"), + QByteArrayLiteral("bottom_left_corner"), + }, + }, + { + QByteArrayLiteral("se-resize"), + { + QByteArrayLiteral("size_fdiag"), + QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"), + QByteArrayLiteral("bd_double_arrow"), + QByteArrayLiteral("bottom_right_corner"), + }, + }, + { + QByteArrayLiteral("ne-resize"), + { + QByteArrayLiteral("size_bdiag"), + QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"), + QByteArrayLiteral("fd_double_arrow"), + QByteArrayLiteral("top_right_corner"), + }, + }, + { + QByteArrayLiteral("nw-resize"), + { + QByteArrayLiteral("size_fdiag"), + QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"), + QByteArrayLiteral("bd_double_arrow"), + QByteArrayLiteral("top_left_corner"), + }, + }, + { + QByteArrayLiteral("n-resize"), + { + QByteArrayLiteral("size_ver"), + QByteArrayLiteral("00008160000006810000408080010102"), + QByteArrayLiteral("sb_v_double_arrow"), + QByteArrayLiteral("v_double_arrow"), + QByteArrayLiteral("col-resize"), + QByteArrayLiteral("top_side"), + }, + }, + { + QByteArrayLiteral("e-resize"), + { + QByteArrayLiteral("size_hor"), + QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"), + QByteArrayLiteral("sb_h_double_arrow"), + QByteArrayLiteral("h_double_arrow"), + QByteArrayLiteral("row-resize"), + QByteArrayLiteral("left_side"), + }, + }, + { + QByteArrayLiteral("s-resize"), + { + QByteArrayLiteral("size_ver"), + QByteArrayLiteral("00008160000006810000408080010102"), + QByteArrayLiteral("sb_v_double_arrow"), + QByteArrayLiteral("v_double_arrow"), + QByteArrayLiteral("col-resize"), + QByteArrayLiteral("bottom_side"), + }, + }, + { + QByteArrayLiteral("w-resize"), + { + QByteArrayLiteral("size_hor"), + QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"), + QByteArrayLiteral("sb_h_double_arrow"), + QByteArrayLiteral("h_double_arrow"), + QByteArrayLiteral("right_side"), + }, + }, + }; + auto it = alternatives.find(name); + if (it != alternatives.end()) { + return it.value(); + } + return QByteArrayList(); +} + +namespace Aurora { + +namespace Core { + +/* + * ShapeCursorSourcePrivate + */ + +class ShapeCursorSourcePrivate +{ + Q_DECLARE_PUBLIC(ShapeCursorSource) +public: + explicit ShapeCursorSourcePrivate(ShapeCursorSource *self); + + void refresh(); + void selectSprite(int index); + void selectNextSprite(); + + QSizeF size; + QPointF hotSpot; + + XcursorTheme xcursorTheme; + QTimer delayTimer; + QImage image; + QByteArray shape; + int currentSprite = -1; + QVector sprites; + +protected: + ShapeCursorSource *q_ptr = nullptr; +}; + +ShapeCursorSourcePrivate::ShapeCursorSourcePrivate(ShapeCursorSource *self) + : q_ptr(self) +{ + delayTimer.setSingleShot(true); + QObject::connect(&delayTimer, SIGNAL(timeout()), self, SLOT(selectNextSprite())); +} + +void ShapeCursorSourcePrivate::refresh() +{ + currentSprite = -1; + delayTimer.stop(); + + sprites = xcursorTheme.shape(shape); + if (sprites.isEmpty()) { + const auto alternativeNames = cursorAlternativeNames(shape); + for (const QByteArray &alternativeName : alternativeNames) { + sprites = xcursorTheme.shape(alternativeName); + if (!sprites.isEmpty()) + break; + } + } + + if (!sprites.isEmpty()) + selectSprite(0); +} + +void ShapeCursorSourcePrivate::selectSprite(int index) +{ + Q_Q(ShapeCursorSource); + + if (currentSprite == index) + return; + + const XcursorSprite &sprite = sprites[index]; + currentSprite = index; + image = sprite.data(); + size = QSizeF(image.size()) / image.devicePixelRatio(); + hotSpot = sprite.hotSpot(); + + if (sprite.delay().count() && sprites.size() > 1) + delayTimer.start(sprite.delay()); + + emit q->changed(); +} + +void ShapeCursorSourcePrivate::selectNextSprite() +{ + selectSprite((currentSprite + 1) % sprites.size()); +} + +/* + * ShapeCursorSource + */ + +ShapeCursorSource::ShapeCursorSource(QObject *parent) + : CursorSource(parent) + , d_ptr(new ShapeCursorSourcePrivate(this)) +{ +} + +ShapeCursorSource::~ShapeCursorSource() +{ +} + +QSizeF ShapeCursorSource::size() const +{ + Q_D(const ShapeCursorSource); + return d->size; +} + +QPointF ShapeCursorSource::hotSpot() const +{ + Q_D(const ShapeCursorSource); + return d->hotSpot; +} + +QImage ShapeCursorSource::image() const +{ + Q_D(const ShapeCursorSource); + return d->image; +} + +QByteArray ShapeCursorSource::shape() const +{ + Q_D(const ShapeCursorSource); + return d->shape; +} + +void ShapeCursorSource::setShape(const QByteArray &shape) +{ + Q_D(ShapeCursorSource); + + if (d->shape != shape) { + d->shape = shape; + d->refresh(); + } +} + +void ShapeCursorSource::setShape(Qt::CursorShape shape) +{ + setShape(cursorShapeToName(shape)); +} + +void ShapeCursorSource::loadTheme(const QString &themeName, int size, qreal devicePixelRatio) +{ + Q_D(ShapeCursorSource); + d->xcursorTheme = XcursorTheme(themeName, size, devicePixelRatio); +} + +} // namespace Core + +} // namespace Aurora + +#include "moc_shapecursorsource.cpp" \ No newline at end of file diff --git a/src/core/shapecursorsource.h b/src/core/shapecursorsource.h new file mode 100644 index 00000000..260c67e9 --- /dev/null +++ b/src/core/shapecursorsource.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2022 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Core { + +class ShapeCursorSourcePrivate; + +class LIRIAURORACORE_EXPORT ShapeCursorSource : public CursorSource +{ + Q_OBJECT + Q_DECLARE_PRIVATE(ShapeCursorSource) +public: + explicit ShapeCursorSource(QObject *parent = nullptr); + ~ShapeCursorSource(); + + QSizeF size() const override; + QPointF hotSpot() const override; + + QImage image() const; + + QByteArray shape() const; + void setShape(const QByteArray &shape); + void setShape(Qt::CursorShape shape); + + void loadTheme(const QString &themeName, int size, qreal devicePixelRatio); + +protected: + QScopedPointer const d_ptr; + +private: + Q_PRIVATE_SLOT(d_func(), void selectNextSprite()) +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/core/xcursor.c b/src/core/xcursor.c new file mode 100644 index 00000000..9bc5bc70 --- /dev/null +++ b/src/core/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/core/xcursor.h b/src/core/xcursor.h new file mode 100644 index 00000000..78f377d5 --- /dev/null +++ b/src/core/xcursor.h @@ -0,0 +1,70 @@ +/* + * 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; + +XcursorImages * +XcursorFileLoadImages (const char *file, int size); + +void +XcursorImagesDestroy (XcursorImages *images); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/core/xcursortheme.cpp b/src/core/xcursortheme.cpp new file mode 100644 index 00000000..437ee036 --- /dev/null +++ b/src/core/xcursortheme.cpp @@ -0,0 +1,255 @@ +// 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_p.h" + +namespace Aurora { + +namespace Core { + +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).constData(), 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); +} + +} // namespace Core + +} // namespace Aurora diff --git a/src/core/xcursortheme_p.h b/src/core/xcursortheme_p.h new file mode 100644 index 00000000..c1a203d4 --- /dev/null +++ b/src/core/xcursortheme_p.h @@ -0,0 +1,74 @@ +// 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 + +// +// 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 Core { + +class XcursorSpritePrivate; +class XcursorThemePrivate; + +class 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 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; + +private: + QSharedDataPointer d; +}; + +} // namespace Core + +} // namespace Aurora diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt new file mode 100644 index 00000000..cf668e92 --- /dev/null +++ b/src/platform/CMakeLists.txt @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +# SPDX-License-Identifier: BSD-3-Clause + +include(ECMQtDeclareLoggingCategory) +ecm_qt_declare_logging_category( + AuroraPlatform_SOURCES + HEADER "auroraplatformloggingcategories.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 + edid.cpp edid.h + eglconfigchooser.cpp eglconfigchooser_p.h + eglfsdeviceintegration.cpp eglfsdeviceintegration_p.h + inputdevice.cpp inputdevice.h + inputmanager.cpp inputmanager.h + keyboarddevice.cpp keyboarddevice.h keyboarddevice_p.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 + ${AuroraPlatform_SOURCES} + LIBRARIES + Liri::AuroraXkbCommonSupport + Liri::AuroraXkbCommonSupportPrivate + EGL::EGL + PkgConfig::LibDisplayInfo + PUBLIC_LIBRARIES + Qt6::Core + Qt6::Gui + Liri::AuroraCore +) + +liri_finalize_module(AuroraPlatform) diff --git a/src/platform/c_ptr_p.h b/src/platform/c_ptr_p.h new file mode 100644 index 00000000..282458a0 --- /dev/null +++ b/src/platform/c_ptr_p.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2022 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +struct CDeleter +{ + template + void operator()(T *ptr) + { + if (ptr) + free(ptr); + } +}; + +template +using UniqueCPtr = std::unique_ptr; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/deviceintegration.cpp b/src/platform/deviceintegration.cpp new file mode 100644 index 00000000..b59f4177 --- /dev/null +++ b/src/platform/deviceintegration.cpp @@ -0,0 +1,128 @@ +// 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; + Q_EMIT statusChanged(status); +} + +bool DeviceIntegration::supportsPBuffers() +{ + return true; +} + +bool DeviceIntegration::supportsSurfacelessContexts() +{ + return true; +} + +EGLNativeDisplayType DeviceIntegration::platformDisplay() const +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLDisplay DeviceIntegration::eglDisplay() const +{ + return EGL_NO_DISPLAY; +} + +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) +{ + 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(QObject *parent) +{ + Q_UNUSED(parent) + 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..b7c13985 --- /dev/null +++ b/src/platform/deviceintegration.h @@ -0,0 +1,84 @@ +// 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 eglDisplay() const; + + 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(QObject *parent = nullptr); + + virtual Outputs outputs() const = 0; + +Q_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..68c51b25 --- /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 "auroraplatformloggingcategories.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/edid.cpp b/src/platform/edid.cpp new file mode 100644 index 00000000..fd331397 --- /dev/null +++ b/src/platform/edid.cpp @@ -0,0 +1,285 @@ +// SPDX-FileCopyrightText: 2024 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2015 Martin Flöser +// SPDX-FileCopyrightText: 2019 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "c_ptr_p.h" +#include "auroraplatformloggingcategories.h" +#include "edid.h" + +extern "C" { +#include +#include +#include +} + +namespace Aurora { + +namespace Platform { + +static QByteArray parsePnpId(const uint8_t *data) +{ + // Decode PNP ID from three 5 bit words packed into 2 bytes: + // + // | Byte | Bit | + // | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + // ---------------------------------------- + // | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | + // | | * | Character 1 | Char 2| + // ---------------------------------------- + // | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| + // | | Character2| Character 3 | + // ---------------------------------------- + const uint offset = 0x8; + + char pnpId[4]; + pnpId[0] = 'A' + ((data[offset + 0] >> 2) & 0x1f) - 1; + pnpId[1] = 'A' + (((data[offset + 0] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; + pnpId[2] = 'A' + (data[offset + 1] & 0x1f) - 1; + pnpId[3] = '\0'; + + return QByteArray(pnpId); +} + +static QByteArray parseEisaId(const uint8_t *data) +{ + for (int i = 72; i <= 108; i += 18) { + // Skip the block if it isn't used as monitor descriptor. + if (data[i]) { + continue; + } + if (data[i + 1]) { + continue; + } + + // We have found the EISA ID, it's stored as ASCII. + if (data[i + 3] == 0xfe) { + return QByteArray(reinterpret_cast(&data[i + 5]), 13).trimmed(); + } + } + + // If there isn't an ASCII EISA ID descriptor, try to decode PNP ID + return parsePnpId(data); +} + +static QByteArray parseVendor(const uint8_t *data) +{ + const auto pnpId = parsePnpId(data); + + // Map to vendor name + QFile pnpFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QStringLiteral("hwdata/pnp.ids"))); + if (pnpFile.exists() && pnpFile.open(QIODevice::ReadOnly)) { + while (!pnpFile.atEnd()) { + const auto line = pnpFile.readLine(); + if (line.startsWith(pnpId)) { + return line.mid(4).trimmed(); + } + } + } + + return {}; +} + +Edid::Edid() +{ +} + +Edid::Edid(const void *data, uint32_t size) +{ + m_raw.resize(size); + memcpy(m_raw.data(), data, size); + + const uint8_t *bytes = static_cast(data); + + auto info = di_info_parse_edid(data, size); + if (!info) { + qCWarning(gLcAuroraPlatform, "Parsing edid failed"); + return; + } + const di_edid *edid = di_info_get_edid(info); + const di_edid_vendor_product *productInfo = di_edid_get_vendor_product(edid); + const di_edid_screen_size *screenSize = di_edid_get_screen_size(edid); + + // basic output information + m_physicalSize = QSize(screenSize->width_cm, screenSize->height_cm) * 10; + m_eisaId = parseEisaId(bytes); + UniqueCPtr monitorName{ di_info_get_model(info) }; + m_monitorName = QByteArray(monitorName.get()); + m_serialNumber = QByteArray::number(productInfo->serial); + m_vendor = parseVendor(bytes); + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(m_raw); + m_hash = QString::fromLatin1(hash.result().toHex()); + + m_identifier = QByteArray(productInfo->manufacturer, 3) + " " + + QByteArray::number(productInfo->product) + " " + + QByteArray::number(productInfo->serial) + " " + + QByteArray::number(productInfo->manufacture_week) + " " + + QByteArray::number(productInfo->manufacture_year) + " " + + QByteArray::number(productInfo->model_year); + + // colorimetry and HDR metadata + const auto chromaticity = di_edid_get_chromaticity_coords(edid); + if (chromaticity) { + m_colorimetry = Core::Colorimetry{ + QVector2D{ chromaticity->red_x, chromaticity->red_y }, + QVector2D{ chromaticity->green_x, chromaticity->green_y }, + QVector2D{ chromaticity->blue_x, chromaticity->blue_y }, + QVector2D{ chromaticity->white_x, chromaticity->white_y }, + }; + } else { + m_colorimetry.reset(); + } + + const di_edid_cta *cta = nullptr; + const di_edid_ext *const *exts = di_edid_get_extensions(edid); + const di_cta_hdr_static_metadata_block *hdr_static_metadata = nullptr; + const di_cta_colorimetry_block *colorimetry = nullptr; + for (; *exts != nullptr; exts++) { + if ((cta = di_edid_ext_get_cta(*exts))) { + break; + } + } + if (cta) { + const di_cta_data_block *const *blocks = di_edid_cta_get_data_blocks(cta); + for (; *blocks != nullptr; blocks++) { + if (!hdr_static_metadata + && (hdr_static_metadata = di_cta_data_block_get_hdr_static_metadata(*blocks))) { + continue; + } + if (!colorimetry && (colorimetry = di_cta_data_block_get_colorimetry(*blocks))) { + continue; + } + } + if (hdr_static_metadata) { + m_hdrMetadata = HDRMetadata{ + .desiredContentMinLuminance = hdr_static_metadata->desired_content_min_luminance, + .desiredContentMaxLuminance = hdr_static_metadata->desired_content_max_luminance > 0 + ? std::make_optional(hdr_static_metadata->desired_content_max_luminance) + : std::nullopt, + .desiredMaxFrameAverageLuminance = + hdr_static_metadata->desired_content_max_frame_avg_luminance > 0 + ? std::make_optional( + hdr_static_metadata->desired_content_max_frame_avg_luminance) + : std::nullopt, + .supportsPQ = hdr_static_metadata->eotfs->pq, + .supportsBT2020 = colorimetry && colorimetry->bt2020_rgb, + }; + } + } + + m_isValid = true; + di_info_destroy(info); +} + +bool Edid::isValid() const +{ + return m_isValid; +} + +QSize Edid::physicalSize() const +{ + return m_physicalSize; +} + +QByteArray Edid::eisaId() const +{ + return m_eisaId; +} + +QByteArray Edid::monitorName() const +{ + return m_monitorName; +} + +QByteArray Edid::serialNumber() const +{ + return m_serialNumber; +} + +QByteArray Edid::vendor() const +{ + return m_vendor; +} + +QByteArray Edid::raw() const +{ + return m_raw; +} + +QString Edid::manufacturerString() const +{ + QString manufacturer; + if (!m_vendor.isEmpty()) { + manufacturer = QString::fromLatin1(m_vendor); + } else if (!m_eisaId.isEmpty()) { + manufacturer = QString::fromLatin1(m_eisaId); + } + return manufacturer; +} + +QString Edid::nameString() const +{ + if (!m_monitorName.isEmpty()) { + QString m = QString::fromLatin1(m_monitorName); + if (!m_serialNumber.isEmpty()) { + m.append(QLatin1Char('/')); + m.append(QString::fromLatin1(m_serialNumber)); + } + return m; + } else if (!m_serialNumber.isEmpty()) { + return QString::fromLatin1(m_serialNumber); + } else { + return QObject::tr("unknown"); + } +} + +QString Edid::hash() const +{ + return m_hash; +} + +std::optional Edid::colorimetry() const +{ + return m_colorimetry; +} + +double Edid::desiredMinLuminance() const +{ + return m_hdrMetadata ? m_hdrMetadata->desiredContentMinLuminance : 0; +} + +std::optional Edid::desiredMaxFrameAverageLuminance() const +{ + return m_hdrMetadata ? m_hdrMetadata->desiredMaxFrameAverageLuminance : std::nullopt; +} + +std::optional Edid::desiredMaxLuminance() const +{ + return m_hdrMetadata ? m_hdrMetadata->desiredContentMaxLuminance : std::nullopt; +} + +bool Edid::supportsPQ() const +{ + return m_hdrMetadata && m_hdrMetadata->supportsPQ; +} + +bool Edid::supportsBT2020() const +{ + return m_hdrMetadata && m_hdrMetadata->supportsBT2020; +} + +QByteArray Edid::identifier() const +{ + return m_identifier; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/edid.h b/src/platform/edid.h new file mode 100644 index 00000000..6fba1872 --- /dev/null +++ b/src/platform/edid.h @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2024 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2019 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace Aurora { + +namespace Platform { + +/** + * Helper class that can be used for parsing EDID blobs. + * + * http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf + */ +class LIRIAURORAPLATFORM_EXPORT Edid +{ +public: + Edid(); + Edid(const void *data, uint32_t size); + + /** + * Whether this instance of EDID is valid. + */ + bool isValid() const; + + /** + * Returns physical dimensions of the monitor, in millimeters. + */ + QSize physicalSize() const; + + /** + * Returns EISA ID of the manufacturer of the monitor. + */ + QByteArray eisaId() const; + + /** + * Returns the product name of the monitor. + */ + QByteArray monitorName() const; + + /** + * Returns the serial number of the monitor. + */ + QByteArray serialNumber() const; + + /** + * Returns the name of the vendor. + */ + QByteArray vendor() const; + + /** + * Returns the raw edid + */ + QByteArray raw() const; + + /** + * returns the vendor if included, the EISA ID if not + */ + QString manufacturerString() const; + + /** + * returns a string representing the monitor name + * Can be a serial number or "unknown" if the name is empty + */ + QString nameString() const; + + QString hash() const; + + std::optional colorimetry() const; + + double desiredMinLuminance() const; + std::optional desiredMaxFrameAverageLuminance() const; + std::optional desiredMaxLuminance() const; + bool supportsPQ() const; + bool supportsBT2020() const; + + /** + * @returns a string that is intended to identify the monitor uniquely. + * Note that multiple monitors can have the same EDID, so this is not always actually unique + */ + QByteArray identifier() const; + +private: + QSize m_physicalSize; + QByteArray m_vendor; + QByteArray m_eisaId; + QByteArray m_monitorName; + QByteArray m_serialNumber; + QString m_hash; + std::optional m_colorimetry; + struct HDRMetadata + { + double desiredContentMinLuminance; + std::optional desiredContentMaxLuminance; + std::optional desiredMaxFrameAverageLuminance; + bool supportsPQ; + bool supportsBT2020; + }; + std::optional m_hdrMetadata; + + QByteArray m_identifier; + + QByteArray m_raw; + bool m_isValid = false; +}; + +} // namespace Platform + +} // namespace Aurora 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/eglfsdeviceintegration.cpp b/src/platform/eglfsdeviceintegration.cpp new file mode 100644 index 00000000..9c124529 --- /dev/null +++ b/src/platform/eglfsdeviceintegration.cpp @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2024 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "auroraplatformloggingcategories.h" +#include "deviceintegrationplugin.h" +#include "eglfsdeviceintegration_p.h" + +namespace Aurora { + +namespace Platform { + +class EglFSDeviceIntegration +{ +public: + EglFSDeviceIntegration(); + ~EglFSDeviceIntegration(); + + DeviceIntegration *deviceIntegration() const; + +private: + DeviceIntegration *m_integration = nullptr; +}; + +EglFSDeviceIntegration::EglFSDeviceIntegration() +{ + auto integrationKeys = DeviceIntegrationFactory::keys(); + if (!integrationKeys.isEmpty()) { + // Prioritize either Wayland or KMS/DRM + if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { + const auto waylandKey = QStringLiteral("wayland"); + if (integrationKeys.contains(waylandKey)) { + integrationKeys.removeOne(waylandKey); + integrationKeys.prepend(waylandKey); + } + } else { + const auto drmKey = QStringLiteral("drm"); + if (integrationKeys.contains(drmKey)) { + integrationKeys.removeOne(drmKey); + integrationKeys.prepend(drmKey); + } + + const auto waylandKey = QStringLiteral("wayland"); + if (integrationKeys.contains(waylandKey)) + integrationKeys.removeOne(waylandKey); + } + + QByteArray requested; + + // Override with an environment variable + if (qEnvironmentVariableIsSet("AURORA_QPA_INTEGRATION")) + requested = qgetenv("AURORA_QPA_INTEGRATION"); + + if (!requested.isEmpty()) { + const auto requestedString = QString::fromLocal8Bit(requested); + integrationKeys.removeOne(requestedString); + integrationKeys.prepend(requestedString); + } + + qCDebug(gLcAuroraPlatform) << "Device integration plugin keys:" << integrationKeys; + while (!m_integration && !integrationKeys.isEmpty()) { + const auto key = integrationKeys.takeFirst(); + qCDebug(gLcAuroraPlatform) << "Trying to load device integration:" << key; + m_integration = DeviceIntegrationFactory::create(key); + if (m_integration) + qCInfo(gLcAuroraPlatform) << "Loaded device integration" << key; + } + } + + if (!m_integration) + qCFatal(gLcAuroraPlatform, "No suitable device integration found!"); +} + +EglFSDeviceIntegration::~EglFSDeviceIntegration() +{ + if (m_integration) { + m_integration->deleteLater(); + m_integration = nullptr; + } +} + +DeviceIntegration *EglFSDeviceIntegration::deviceIntegration() const +{ + return m_integration; +} + +Q_GLOBAL_STATIC(EglFSDeviceIntegration, eglfsDeviceIntegration) + +DeviceIntegration *auroraDeviceIntegration() +{ + return eglfsDeviceIntegration()->deviceIntegration(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/eglfsdeviceintegration_p.h b/src/platform/eglfsdeviceintegration_p.h new file mode 100644 index 00000000..c44709aa --- /dev/null +++ b/src/platform/eglfsdeviceintegration_p.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +LIRIAURORAPLATFORM_EXPORT DeviceIntegration *auroraDeviceIntegration(); + +} // 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..c3165eea --- /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; + +Q_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..7d7b9042 --- /dev/null +++ b/src/platform/keyboarddevice.cpp @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "auroraplatformloggingcategories.h" +#include "keyboarddevice.h" +#include "keyboarddevice_p.h" + +#include +#include + +namespace Aurora { + +namespace Platform { + +/* + * KeyboardDevicePrivate + */ + +KeyboardDevicePrivate::KeyboardDevicePrivate(KeyboardDevice *self) + : q_ptr(self) +{ + xkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); + if (!xkbContext) + qCWarning(gLcAuroraPlatform) << "Unable to create xkb context"; +} + +#if LIRI_FEATURE_aurora_xkbcommon +bool KeyboardDevicePrivate::createDefaultKeymap() +{ + if (!xkbContext) + return false; + + struct xkb_rule_names names; + names.rules = "evdev"; + names.model = "pc105"; + names.layout = "us"; + names.variant = ""; + names.options = ""; + + xkbKeymap.reset( + xkb_keymap_new_from_names(xkbContext.get(), &names, XKB_KEYMAP_COMPILE_NO_FLAGS)); + if (xkbKeymap) + xkbState.reset(xkb_state_new(xkbKeymap.get())); + + if (!xkbKeymap || !xkbState) { + qCWarning(gLcAuroraPlatform) << "Failed to create default keymap"; + return false; + } + + return true; +} +#endif + +/* + * KeyboardDevice + */ + +KeyboardDevice::KeyboardDevice(QObject *parent) + : InputDevice(parent) + , d_ptr(new KeyboardDevicePrivate(this)) +{ + qRegisterMetaType("KeyboardDevice::KeyEvent"); + + Q_D(KeyboardDevice); + d->repeatTimer.setSingleShot(true); + d->repeatTimer.callOnTimeout(this, [this, d]() { + KeyEvent keyEvent = { d->repeatKey.key, + d->repeatKey.modifiers, + d->repeatKey.code, + d->repeatKey.nativeVirtualKey, + d->repeatKey.nativeModifiers, + d->repeatKey.text, + d->repeatKey.time, + true, + d->repeatKey.repeatCount }; + Q_EMIT keyReleased(keyEvent); + Q_EMIT keyPressed(keyEvent); + + ++d->repeatKey.repeatCount; + d->repeatTimer.setInterval(d->keyRepeatRate); + d->repeatTimer.start(); + }); +} + +KeyboardDevice::~KeyboardDevice() +{ +} + +InputDevice::DeviceType KeyboardDevice::deviceType() +{ + return DeviceType::Keyboard; +} + +bool KeyboardDevice::isKeyRepeatEnabled() const +{ + return false; +} + +qint32 KeyboardDevice::keyRepeatRate() const +{ + Q_D(const KeyboardDevice); + return d->keyRepeatRate; +} + +qint32 KeyboardDevice::keyRepeatDelay() const +{ + Q_D(const KeyboardDevice); + return d->keyRepeatDelay; +} + +Qt::KeyboardModifiers KeyboardDevice::modifiers() const +{ + Q_D(const KeyboardDevice); + +#if LIRI_FEATURE_aurora_xkbcommon + if (!d->xkbState) + return Qt::NoModifier; + return PlatformSupport::XkbCommon::modifiers(d->xkbState.get()); +#else + return Qt::NoModifier; +#endif +} + +void KeyboardDevice::setKeyRepeatEnabled(bool enabled) +{ + Q_D(KeyboardDevice); + + if (d->keyRepeatEnabled != enabled) { + d->keyRepeatEnabled = enabled; + Q_EMIT keyRepeatEnabledChanged(enabled); + + if (!enabled) + d->repeatTimer.stop(); + } +} + +void KeyboardDevice::setKeyRepeatRate(qint32 value) +{ + Q_D(KeyboardDevice); + + if (d->keyRepeatRate != value) { + d->keyRepeatRate = value; + Q_EMIT keyRepeatRateChanged(value); + } +} + +void KeyboardDevice::setKeyRepeatDelay(qint32 value) +{ + Q_D(KeyboardDevice); + + if (d->keyRepeatDelay != value) { + d->keyRepeatDelay = value; + Q_EMIT keyRepeatDelayChanged(value); + } +} + +void KeyboardDevice::handleKeymapChanged(int fd, quint32 size) +{ +#if LIRI_FEATURE_aurora_xkbcommon + Q_D(KeyboardDevice); + + char *map_str = static_cast(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0)); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + d->xkbKeymap.reset(xkb_keymap_new_from_string( + d->xkbContext.get(), map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS)); + PlatformSupport::XkbCommon::verifyHasLatinLayout(d->xkbKeymap.get()); + + munmap(map_str, size); + close(fd); + + if (d->xkbKeymap) + d->xkbState.reset(xkb_state_new(d->xkbKeymap.get())); + else + d->xkbState.reset(nullptr); +#else + Q_UNUSED(fd) + Q_UNUSED(size) +#endif +} + +void KeyboardDevice::handleKeyChanged(quint32 key, const KeyState &keyState, quint32 time) +{ +#if LIRI_FEATURE_aurora_xkbcommon + Q_D(KeyboardDevice); + + // Make sure we have a keymap loaded + if (!d->xkbKeymap || !d->xkbState) { + if (!d->createDefaultKeymap()) + return; + } + + // Sanity check + if (!d->xkbKeymap || !d->xkbState) + return; + + const auto isPressed = keyState == KeyboardDevice::KeyState::Pressed; + + auto code = key + 8; + auto qtModifiers = modifiers(); + auto sym = xkb_state_key_get_one_sym(d->xkbState.get(), code); + auto qtKey = + PlatformSupport::XkbCommon::keysymToQtKey(sym, modifiers(), d->xkbState.get(), code); + auto text = PlatformSupport::XkbCommon::lookupString(d->xkbState.get(), code); + + xkb_state_update_key(d->xkbState.get(), code, isPressed ? XKB_KEY_DOWN : XKB_KEY_UP); + + KeyEvent keyEvent = { qtKey, qtModifiers, code, sym, d->nativeModifiers, text, time, false, 1 }; + if (isPressed) + Q_EMIT keyPressed(keyEvent); + else if (keyState == KeyboardDevice::KeyState::Released) + Q_EMIT keyReleased(keyEvent); + + if (isPressed && d->keyRepeatEnabled && d->keyRepeatRate > 0 + && xkb_keymap_key_repeats(d->xkbKeymap.get(), code)) { + d->repeatKey.key = qtKey; + d->repeatKey.code = code; + d->repeatKey.time = time; + d->repeatKey.text = text; + d->repeatKey.modifiers = qtModifiers; + d->repeatKey.nativeModifiers = d->nativeModifiers; + d->repeatKey.nativeVirtualKey = sym; + d->repeatKey.repeatCount = 1; + d->repeatTimer.setInterval(d->keyRepeatDelay); + d->repeatTimer.start(); + } else if (d->repeatTimer.isActive()) { + d->repeatTimer.stop(); + } +#else + Q_UNUSED(key) + Q_UNUSED(keyState) + Q_UNUSED(time) +#endif +} + +void KeyboardDevice::handleModifiers(quint32 depressed, quint32 latched, quint32 locked, + quint32 group) +{ +#if LIRI_FEATURE_aurora_xkbcommon + Q_D(KeyboardDevice); + + if (d->xkbState) + xkb_state_update_mask(d->xkbState.get(), depressed, latched, locked, 0, 0, group); + d->nativeModifiers = depressed | latched | locked; +#else + Q_UNUSED(depressed) + Q_UNUSED(latched) + Q_UNUSED(locked) + Q_UNUSED(group) +#endif +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/keyboarddevice.h b/src/platform/keyboarddevice.h new file mode 100644 index 00000000..989e6e53 --- /dev/null +++ b/src/platform/keyboarddevice.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class KeyboardDevicePrivate; + +class LIRIAURORAPLATFORM_EXPORT KeyboardDevice : public InputDevice +{ + Q_OBJECT + Q_PROPERTY(bool keyRepeatEnabled READ isKeyRepeatEnabled NOTIFY keyRepeatEnabledChanged) + Q_PROPERTY(qint32 keyRepeatRate READ keyRepeatRate NOTIFY keyRepeatRateChanged) + Q_PROPERTY(qint32 keyRepeatDelay READ keyRepeatDelay NOTIFY keyRepeatDelayChanged) + Q_DECLARE_PRIVATE(KeyboardDevice) +public: + enum class KeyState { + Released, + Pressed, + }; + Q_ENUM(KeyState) + + class KeyEvent + { + public: + int key; + Qt::KeyboardModifiers modifiers; + quint32 nativeScanCode; + quint32 nativeVirtualKey; + quint32 nativeModifiers; + QString text; + quint32 timestamp; + bool autoRepeat; + ushort repeatCount; + }; + + explicit KeyboardDevice(QObject *parent = nullptr); + ~KeyboardDevice(); + + DeviceType deviceType() override; + + bool isKeyRepeatEnabled() const; + qint32 keyRepeatRate() const; + qint32 keyRepeatDelay() const; + + Qt::KeyboardModifiers modifiers() const; + +protected: + QScopedPointer const d_ptr; + + void setKeyRepeatEnabled(bool enabled); + void setKeyRepeatRate(qint32 value); + void setKeyRepeatDelay(qint32 value); + + void handleKeymapChanged(int fd, quint32 size); + void handleKeyChanged(quint32 key, const KeyState &keyState, quint32 time); + void handleModifiers(quint32 depressed, quint32 latched, quint32 locked, quint32 group); + +Q_SIGNALS: + void keyPressed(const KeyboardDevice::KeyEvent &event); + void keyReleased(const KeyboardDevice::KeyEvent &event); + void keyRepeatEnabledChanged(bool enabled); + void keyRepeatRateChanged(qint32 rate); + void keyRepeatDelayChanged(qint32 delay); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/keyboarddevice_p.h b/src/platform/keyboarddevice_p.h new file mode 100644 index 00000000..f42a166d --- /dev/null +++ b/src/platform/keyboarddevice_p.h @@ -0,0 +1,71 @@ +// 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 +#include + +#include +#if LIRI_FEATURE_aurora_xkbcommon +# include +#endif +#include + +namespace Aurora { + +namespace Platform { + +class KeyboardDevice; + +class LIRIAURORAPLATFORM_EXPORT KeyboardDevicePrivate +{ + Q_DECLARE_PUBLIC(KeyboardDevice) +public: + explicit KeyboardDevicePrivate(KeyboardDevice *self); + +#if LIRI_FEATURE_aurora_xkbcommon + bool createDefaultKeymap(); + + PlatformSupport::XkbCommon::ScopedXKBContext xkbContext; + PlatformSupport::XkbCommon::ScopedXKBKeymap xkbKeymap; + PlatformSupport::XkbCommon::ScopedXKBState xkbState; +#endif + + quint32 nativeModifiers = 0; + + bool keyRepeatEnabled = false; + qint32 keyRepeatRate = 25; + qint32 keyRepeatDelay = 400; + + struct RepeatKey + { + int key = 0; + quint32 code = 0; + quint32 time = 0; + QString text; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + quint32 nativeVirtualKey = 0; + quint32 nativeModifiers = 0; + ushort repeatCount; + } repeatKey; + QTimer repeatTimer; + +protected: + KeyboardDevice *q_ptr = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora \ No newline at end of file diff --git a/src/platform/output.cpp b/src/platform/output.cpp new file mode 100644 index 00000000..827ef1cf --- /dev/null +++ b/src/platform/output.cpp @@ -0,0 +1,457 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2018 Roman Gilg +// SPDX-License-Identifier: GPL-2.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 +{ + Q_D(const Output); + return d->information.name; +} + +/*! + * \property Output::description + * \brief Human readable description of the output. + * + * This property contains a human readable description of he output. + */ +QString Output::description() const +{ + Q_D(const Output); + + if (d->information.description.isEmpty()) + return manufacturer() + QLatin1Char(' ') + model(); + return d->information.description; +} + +/*! + * \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::screen + * \brief The QScreen object corresponding tothe output. + * + * This property indicates the QScreen associated with this output. + */ +QScreen *Output::screen() const +{ + Q_D(const Output); + return d->screen; +} + +void Output::setScreen(QScreen *screen) +{ + Q_D(Output); + + if (d->screen != screen) { + d->screen = screen; + Q_EMIT screenChanged(screen); + } +} + +/*! + * \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->information.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->information.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->information.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->information.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); + } + + return QRect(); +} + +/*! + * \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::modes + * \brief Modes + * + * This property holds the list of modes of the output. + */ +QList Output::modes() const +{ + Q_D(const Output); + return d->modes; +} + +/*! + * \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; + Q_EMIT powerStateChanged(powerState); +} + +Output::Subpixel Output::subpixel() const +{ + Q_D(const Output); + return d->information.subpixel; +} + +Output::Transform Output::transform() const +{ + Q_D(const Output); + return d->state.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; + Q_EMIT contentTypeChanged(contentType); +} + +void Output::setInformation(const Information &information) +{ + Q_D(Output); + + d->information = information; + // d->uuid = generateOutputId(eisaId(), model(), serialNumber(), name()); +} + +void Output::setState(const State &state) +{ +} + +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::setGlobalPosition(const QPoint &globalPosition) +{ + Q_Q(Output); + + if (this->globalPosition != globalPosition) { + this->globalPosition = globalPosition; + Q_EMIT q->globalPositionChanged(globalPosition); + } +} + +void OutputPrivate::setScale(qreal scale) +{ + Q_Q(Output); + + if (this->scale != scale) { + this->scale = scale; + Q_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..9828fd14 --- /dev/null +++ b/src/platform/output.h @@ -0,0 +1,267 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2019 Roman Gilg +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#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(QScreen *screen READ screen NOTIFY screenChanged) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString manufacturer READ manufacturer CONSTANT) + Q_PROPERTY(QString model READ model CONSTANT) + Q_PROPERTY(QString serialNumber READ serialNumber CONSTANT) + Q_PROPERTY(QSize physicalSize READ physicalSize CONSTANT) + 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 CONSTANT) + 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, + AutoRotation = 1 << 6, + IccProfile = 1 << 7, + Tearing = 1 << 8, + }; + 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) + + enum class RgbRange { + Automatic, + Full, + Limited, + }; + Q_ENUM(RgbRange) + + enum class AutoRotationPolicy { + Never, + InTabletMode, + Always + }; + Q_ENUM(AutoRotationPolicy) + + 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; + + QScreen *screen() const; + void setScreen(QScreen *screen); + + QString name() const; + 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; + + QList modes() const; + + PowerState powerState() const; + void setPowerState(PowerState powerState); + + Subpixel subpixel() const; + + Transform transform() const; + + ContentType contentType() const; + void setContentType(ContentType contentType); + +Q_SIGNALS: + void screenChanged(QScreen *screen); + 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 transformChanged(Output::Transform transform); + void contentTypeChanged(Output::ContentType contentType); + void modeAdded(const Mode &mode); + void modeChanged(const Mode &mode); + +protected: + QScopedPointer const d_ptr; + + struct Information + { + QString name; + QString description; + QString manufacturer; + QString model; + QString serialNumber; + QString eisaId; + QSize physicalSize; + Edid edid; + Subpixel subpixel = Subpixel::Unknown; + Capabilities capabilities; + Transform panelOrientation = Transform::Normal; + bool internal = false; + bool placeholder = false; + bool nonDesktop = false; + QByteArray mstPath; + std::optional maxPeakBrightness; + std::optional maxAverageBrightness; + double minBrightness = 0; + }; + + struct State + { + QPoint position; + qreal scale = 1; + Transform transform = Transform::Normal; + Transform manualTransform = Transform::Normal; + // QList> modes; + // std::shared_ptr currentMode; + PowerState powerState = PowerState::On; + Subpixel subpixel = Subpixel::Unknown; + bool enabled = false; + uint32_t overscan = 0; + RgbRange rgbRange = RgbRange::Automatic; + bool wideColorGamut = false; + bool highDynamicRange = false; + uint32_t sdrBrightness = 200; + AutoRotationPolicy autoRotatePolicy = AutoRotationPolicy::InTabletMode; + QString iccProfilePath; + // std::shared_ptr iccProfile; + // ColorDescription colorDescription = ColorDescription::sRGB; + std::optional maxPeakBrightnessOverride; + std::optional maxAverageBrightnessOverride; + std::optional minBrightnessOverride; + double sdrGamutWideness = 0; + // VrrPolicy vrrPolicy = VrrPolicy::Automatic; + }; + + void setInformation(const Information &information); + void setState(const State &state); +}; + +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..d081e641 --- /dev/null +++ b/src/platform/output_p.h @@ -0,0 +1,58 @@ +// 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 setGlobalPosition(const QPoint &globalPosition); + void setScale(qreal scale); + + QUuid uuid; + QScreen *screen = nullptr; + 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::ContentType contentType = Output::ContentType::Unknown; + QList modes; + QList::iterator currentMode = modes.end(); + + Output::Information information; + Output::State state; + +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..a31d9c18 --- /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; + +Q_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..4e0a46f1 --- /dev/null +++ b/src/platform/session.cpp @@ -0,0 +1,132 @@ +// 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 }, +}; + +Session::Session(QObject *parent) + : QObject(parent) +{ +} + +Session *Session::create(QObject *parent) +{ + for (const auto &sessionInfo : s_availableSessions) { + auto *session = sessionInfo.createFunc(parent); + 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..a2a3312f --- /dev/null +++ b/src/platform/session.h @@ -0,0 +1,64 @@ +// 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 QString name() const = 0; + + 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 Session *create(QObject *parent = nullptr); + +Q_SIGNALS: + void activeChanged(bool active); + void awoke(); + void deviceResumed(dev_t deviceId); + void devicePaused(dev_t deviceId); + +protected: + explicit Session(QObject *parent = nullptr); +}; + +} // 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..5871ce15 --- /dev/null +++ b/src/platform/session_noop.cpp @@ -0,0 +1,65 @@ +// 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 { + +NoopSession::NoopSession(QObject *parent) + : Session(parent) +{ +} + +NoopSession::~NoopSession() +{ +} + +QString NoopSession::name() const +{ + return QStringLiteral("noop"); +} + +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) +{ +} + +NoopSession *NoopSession::create(QObject *parent) +{ + return new NoopSession(parent); +} + +} // 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..9bef634b --- /dev/null +++ b/src/platform/session_noop_p.h @@ -0,0 +1,52 @@ +// 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: + ~NoopSession() override; + + QString name() const 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; + + static NoopSession *create(QObject *parent = nullptr); + +private: + explicit NoopSession(QObject *parent = nullptr); +}; + +} // 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