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