From e45e85bfbaed8aa86ff45025e74923828816c2b7 Mon Sep 17 00:00:00 2001 From: Pier Luigi Fiorini Date: Thu, 3 Aug 2023 20:17:31 +0200 Subject: [PATCH] Say hello to AuroraPlatform and AuroraCore AuroraPlatform is a platform abstraction library that glues together compositors with EGL device integration and input device managers. Compositors will use the platform abstraction layer to access input and output devices directly. AuroraCore has utilities for compositors and device integration plugins. Closes: #41 --- CMakeLists.txt | 2 + src/core/CMakeLists.txt | 30 ++ src/core/cursorsource.cpp | 28 ++ src/core/cursorsource.h | 32 ++ src/core/shapecursorsource.cpp | 500 ++++++++++++++++++++ src/core/shapecursorsource.h | 43 ++ src/core/xcursor.c | 551 +++++++++++++++++++++++ src/core/xcursor.h | 70 +++ src/core/xcursortheme.cpp | 255 +++++++++++ src/core/xcursortheme_p.h | 74 +++ src/platform/CMakeLists.txt | 41 ++ src/platform/deviceintegration.cpp | 130 ++++++ src/platform/deviceintegration.h | 84 ++++ src/platform/deviceintegration_p.h | 37 ++ src/platform/deviceintegrationplugin.cpp | 89 ++++ src/platform/deviceintegrationplugin.h | 37 ++ src/platform/eglconfigchooser.cpp | 85 ++++ src/platform/eglconfigchooser_p.h | 21 + src/platform/inputdevice.cpp | 17 + src/platform/inputdevice.h | 37 ++ src/platform/inputmanager.cpp | 38 ++ src/platform/inputmanager.h | 43 ++ src/platform/keyboarddevice.cpp | 258 +++++++++++ src/platform/keyboarddevice.h | 76 ++++ src/platform/keyboarddevice_p.h | 71 +++ src/platform/output.cpp | 453 +++++++++++++++++++ src/platform/output.h | 193 ++++++++ src/platform/output_p.h | 65 +++ src/platform/pointerdevice.cpp | 22 + src/platform/pointerdevice.h | 28 ++ src/platform/session.cpp | 132 ++++++ src/platform/session.h | 62 +++ src/platform/session_noop.cpp | 60 +++ src/platform/session_noop_p.h | 50 ++ src/platform/touchdevice.cpp | 22 + src/platform/touchdevice.h | 23 + src/platform/window.cpp | 58 +++ src/platform/window.h | 45 ++ src/platform/window_p.h | 47 ++ 39 files changed, 3909 insertions(+) create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/cursorsource.cpp create mode 100644 src/core/cursorsource.h create mode 100644 src/core/shapecursorsource.cpp create mode 100644 src/core/shapecursorsource.h create mode 100644 src/core/xcursor.c create mode 100644 src/core/xcursor.h create mode 100644 src/core/xcursortheme.cpp create mode 100644 src/core/xcursortheme_p.h create mode 100644 src/platform/CMakeLists.txt create mode 100644 src/platform/deviceintegration.cpp create mode 100644 src/platform/deviceintegration.h create mode 100644 src/platform/deviceintegration_p.h create mode 100644 src/platform/deviceintegrationplugin.cpp create mode 100644 src/platform/deviceintegrationplugin.h create mode 100644 src/platform/eglconfigchooser.cpp create mode 100644 src/platform/eglconfigchooser_p.h create mode 100644 src/platform/inputdevice.cpp create mode 100644 src/platform/inputdevice.h create mode 100644 src/platform/inputmanager.cpp create mode 100644 src/platform/inputmanager.h create mode 100644 src/platform/keyboarddevice.cpp create mode 100644 src/platform/keyboarddevice.h create mode 100644 src/platform/keyboarddevice_p.h create mode 100644 src/platform/output.cpp create mode 100644 src/platform/output.h create mode 100644 src/platform/output_p.h create mode 100644 src/platform/pointerdevice.cpp create mode 100644 src/platform/pointerdevice.h create mode 100644 src/platform/session.cpp create mode 100644 src/platform/session.h create mode 100644 src/platform/session_noop.cpp create mode 100644 src/platform/session_noop_p.h create mode 100644 src/platform/touchdevice.cpp create mode 100644 src/platform/touchdevice.h create mode 100644 src/platform/window.cpp create mode 100644 src/platform/window.h create mode 100644 src/platform/window_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 95511845..6984b300 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,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..c434d567 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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 + cursorsource.cpp cursorsource.h + shapecursorsource.cpp shapecursorsource.h + xcursor.c xcursor.h + xcursortheme.cpp xcursortheme_p.h + ${AuroraCore_SOURCES} + DEFINES + QT_NO_CAST_FROM_ASCII + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui +) + +liri_finalize_module(AuroraCore) 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..e1b43e56 --- /dev/null +++ b/src/core/shapecursorsource.cpp @@ -0,0 +1,500 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2022 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#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 QVector 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 QVector(); +} + +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..0ca2123d --- /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), 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..a2caa5c3 --- /dev/null +++ b/src/platform/CMakeLists.txt @@ -0,0 +1,41 @@ +# 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 + eglconfigchooser.cpp eglconfigchooser_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} + DEFINES + QT_NO_CAST_FROM_ASCII + LIBRARIES + Liri::AuroraXkbCommonSupport + Liri::AuroraXkbCommonSupportPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui +) + +liri_finalize_module(AuroraPlatform) diff --git a/src/platform/deviceintegration.cpp b/src/platform/deviceintegration.cpp new file mode 100644 index 00000000..913d8a1f --- /dev/null +++ b/src/platform/deviceintegration.cpp @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "deviceintegration.h" +#include "deviceintegration_p.h" +#include "eglconfigchooser_p.h" +#include "inputmanager.h" + +namespace Aurora { + +namespace Platform { + +/* + * DeviceIntegration + */ + +DeviceIntegration::DeviceIntegration(QObject *parent) + : QObject(parent) + , d_ptr(new DeviceIntegrationPrivate(this)) +{ +} + +DeviceIntegration::~DeviceIntegration() +{ +} + +DeviceIntegration::Status DeviceIntegration::status() const +{ + Q_D(const DeviceIntegration); + return d->status; +} + +void DeviceIntegration::setStatus(Status status) +{ + Q_D(DeviceIntegration); + + if (d->status == status) + return; + + d->status = status; + emit statusChanged(status); +} + +bool DeviceIntegration::supportsPBuffers() +{ + return true; +} + +bool DeviceIntegration::supportsSurfacelessContexts() +{ + return true; +} + +EGLNativeDisplayType DeviceIntegration::platformDisplay() const +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLDisplay DeviceIntegration::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) +{ + Q_D(DeviceIntegration); + + QVector configAttribs = eglConfigAttributesFromSurfaceFormat(display, format); + + configAttribs.append(EGL_SURFACE_TYPE); + configAttribs.append(surfaceType()); + + configAttribs.append(EGL_NONE); + + // Get the number of matching configurations for the attributes + EGLConfig config = nullptr; + EGLint numConfigs = 0; + if (!eglChooseConfig(display, configAttribs.constData(), &config, 1, &numConfigs)) + return nullptr; + return config; +} + +Window *DeviceIntegration::getWindow(QWindow *qtWindow) const +{ + Q_UNUSED(qtWindow) + return nullptr; +} + +InputManager *Aurora::Platform::DeviceIntegration::createInputManager(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..16023841 --- /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; + +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/eglconfigchooser.cpp b/src/platform/eglconfigchooser.cpp new file mode 100644 index 00000000..a57318d5 --- /dev/null +++ b/src/platform/eglconfigchooser.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "eglconfigchooser_p.h" + +#ifndef EGL_OPENGL_ES3_BIT_KHR +# define EGL_OPENGL_ES3_BIT_KHR 0x0040 +#endif + +namespace Aurora { + +namespace Platform { + +bool hasEglExtension(EGLDisplay display, const char *name) +{ + QList extensions = + QByteArray(reinterpret_cast(eglQueryString(display, EGL_EXTENSIONS))) + .split(' '); + return extensions.contains(name); +} + +QVector eglConfigAttributesFromSurfaceFormat(EGLDisplay display, + const QSurfaceFormat &format) +{ + QVector configAttribs; + + configAttribs.append(EGL_RED_SIZE); + configAttribs.append(qMax(0, format.redBufferSize())); + + configAttribs.append(EGL_GREEN_SIZE); + configAttribs.append(qMax(0, format.greenBufferSize())); + + configAttribs.append(EGL_BLUE_SIZE); + configAttribs.append(qMax(0, format.blueBufferSize())); + + configAttribs.append(EGL_ALPHA_SIZE); + configAttribs.append(qMax(0, format.alphaBufferSize())); + + configAttribs.append(EGL_SAMPLES); + configAttribs.append(qMax(0, format.samples())); + + configAttribs.append(EGL_SAMPLE_BUFFERS); + configAttribs.append(format.samples() > 0 ? 1 : 0); + + switch (format.renderableType()) { + case QSurfaceFormat::OpenGL: + configAttribs.append(EGL_RENDERABLE_TYPE); + configAttribs.append(EGL_OPENGL_BIT); + break; + case QSurfaceFormat::OpenGLES: + configAttribs.append(EGL_RENDERABLE_TYPE); + if (format.majorVersion() == 1) + configAttribs.append(EGL_OPENGL_ES_BIT); + else if (format.majorVersion() == 2) + configAttribs.append(EGL_OPENGL_ES2_BIT); + else if (format.majorVersion() == 3 && hasEglExtension(display, "EGL_KHR_create_context")) + configAttribs.append(EGL_OPENGL_ES3_BIT_KHR); + else if (format.majorVersion() == 3) + configAttribs.append(EGL_OPENGL_ES3_BIT); + break; + case QSurfaceFormat::OpenVG: + configAttribs.append(EGL_RENDERABLE_TYPE); + configAttribs.append(EGL_OPENVG_BIT); + break; + default: + break; + } + + if (format.renderableType() != QSurfaceFormat::OpenVG) { + configAttribs.append(EGL_DEPTH_SIZE); + configAttribs.append(qMax(0, format.depthBufferSize())); + + configAttribs.append(EGL_STENCIL_SIZE); + configAttribs.append(qMax(0, format.stencilBufferSize())); + } else { + configAttribs.append(EGL_ALPHA_MASK_SIZE); + configAttribs.append(8); + } + + return configAttribs; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/eglconfigchooser_p.h b/src/platform/eglconfigchooser_p.h new file mode 100644 index 00000000..71532feb --- /dev/null +++ b/src/platform/eglconfigchooser_p.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +bool hasEglExtension(EGLDisplay display, const char *name); +QVector eglConfigAttributesFromSurfaceFormat(EGLDisplay display, + const QSurfaceFormat &format); + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputdevice.cpp b/src/platform/inputdevice.cpp new file mode 100644 index 00000000..c5c3593c --- /dev/null +++ b/src/platform/inputdevice.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "inputdevice.h" + +namespace Aurora { + +namespace Platform { + +InputDevice::InputDevice(QObject *parent) + : QObject(parent) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputdevice.h b/src/platform/inputdevice.h new file mode 100644 index 00000000..d2e9066e --- /dev/null +++ b/src/platform/inputdevice.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT InputDevice : public QObject +{ + Q_OBJECT + Q_PROPERTY(DeviceType deviceType READ deviceType CONSTANT) +public: + enum class DeviceType { + Unknown, + Pointer, + Keyboard, + Touch, + Tablet + }; + Q_ENUM(DeviceType) + + explicit InputDevice(QObject *parent = nullptr); + + virtual QString seatName() const = 0; + + virtual DeviceType deviceType() = 0; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputmanager.cpp b/src/platform/inputmanager.cpp new file mode 100644 index 00000000..3288c37c --- /dev/null +++ b/src/platform/inputmanager.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "inputmanager.h" + +namespace Aurora { + +namespace Platform { + +InputManager::InputManager(QObject *parent) + : QObject(parent) +{ +} + +QList InputManager::keyboardDevices() const +{ + return QList(); +} + +QList InputManager::pointerDevices() const +{ + return QList(); +} + +QList InputManager::touchDevices() const +{ + return QList(); +} + +int InputManager::deviceCount(InputDevice::DeviceType deviceType) const +{ + Q_UNUSED(deviceType) + return 0; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/inputmanager.h b/src/platform/inputmanager.h new file mode 100644 index 00000000..e1b6f591 --- /dev/null +++ b/src/platform/inputmanager.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class KeyboardDevice; +class PointerDevice; +class TouchDevice; + +class LIRIAURORAPLATFORM_EXPORT InputManager : public QObject +{ + Q_OBJECT +public: + explicit InputManager(QObject *parent = nullptr); + + virtual QList keyboardDevices() const; + virtual QList pointerDevices() const; + virtual QList touchDevices() const; + + virtual int deviceCount(InputDevice::DeviceType deviceType) const; + +signals: + void deviceAdded(InputDevice *inputDevice); + void deviceRemoved(InputDevice *inputDevice); + void keyboardAdded(KeyboardDevice *keyboardDevice); + void keyboardRemoved(KeyboardDevice *keyboardDevice); + void pointerAdded(PointerDevice *pointerDevice); + void pointerRemoved(PointerDevice *pointerDevice); + void touchAdded(TouchDevice *touchDevice); + void touchRemoved(TouchDevice *touchDevice); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/keyboarddevice.cpp b/src/platform/keyboarddevice.cpp new file mode 100644 index 00000000..a7231666 --- /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 }; + emit keyReleased(keyEvent); + 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; + emit keyRepeatEnabledChanged(enabled); + + if (!enabled) + d->repeatTimer.stop(); + } +} + +void KeyboardDevice::setKeyRepeatRate(qint32 value) +{ + Q_D(KeyboardDevice); + + if (d->keyRepeatRate != value) { + d->keyRepeatRate = value; + emit keyRepeatRateChanged(value); + } +} + +void KeyboardDevice::setKeyRepeatDelay(qint32 value) +{ + Q_D(KeyboardDevice); + + if (d->keyRepeatDelay != value) { + d->keyRepeatDelay = value; + 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) + emit keyPressed(keyEvent); + else if (keyState == KeyboardDevice::KeyState::Released) + 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..a42b6f8f --- /dev/null +++ b/src/platform/keyboarddevice.h @@ -0,0 +1,76 @@ +// 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: + KeyEvent() = default; + + 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); + +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..a962288c --- /dev/null +++ b/src/platform/output.cpp @@ -0,0 +1,453 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include + +#include "output.h" +#include "output_p.h" + +namespace Aurora { + +namespace Platform { + +/*! + \class Output + \inmodule AuroraCore + \brief Generic output representation. + + The Output class represents an output. + */ + +/*! + * Constructs an Output with the given \a parent. + */ +Output::Output(QObject *parent) + : QObject(parent) + , d_ptr(new OutputPrivate(this)) +{ +} + +Output::~Output() +{ +} + +/*! + * \property Output::name + * \brief A user presentable string representing the output. + * + * This property contains a user presentable string representing the output, + * typycally something like "VGA1", "eDP-1", "HDMI1", etc. + */ +QString Output::name() const +{ + return QString(); +} + +/*! + * \property Output::description + * \brief Human readable description of the output. + * + * This property contains a human readable description of he output. + */ +QString Output::description() const +{ + return QString(); +} + +/*! + * \property Output::uuid + * \brief The unique identifier of the output. + * + * This property contains a unique identifier of the output. + */ +QUuid Output::uuid() const +{ + Q_D(const Output); + return d->uuid; +} + +/*! + * \property Output::manufacturer + * \brief The manufacturer of the screen. + * + * This property holds the manufacturer of the screen. + */ +QString Output::manufacturer() const +{ + Q_D(const Output); + return d->manufacturer; +} + +/*! + * \property Output::model + * \brief The model of the screen. + * + * This property holds the model of the screen. + */ +QString Output::model() const +{ + Q_D(const Output); + return d->model; +} + +/*! + * \property Output::serialNumber + * \brief The serial number of the screen. + * + * This property holds the serial number of the screen. + */ +QString Output::serialNumber() const +{ + Q_D(const Output); + return d->serialNumber; +} + +/*! + * \property Output::physicalSize + * \brief The physical size of the screen in millimiters. + * + * This property holds the physical size of the screen in millimiters. + */ +QSize Output::physicalSize() const +{ + Q_D(const Output); + return d->physicalSize; +} + +/*! + * \property Output::enabled + * \brief Weather the output is enable or not. + * + * This property holds weather the output is enabled or not. + */ +bool Output::isEnabled() const +{ + Q_D(const Output); + return d->enabled; +} + +/*! + * \property Output::globalPosition + * \brief Position in the global compositor space. + * + * This property holds the output position within the global compositor space. + */ +QPoint Output::globalPosition() const +{ + Q_D(const Output); + return d->globalPosition; +} + +/*! + * \property Output::pixelSize + * \brief Size. + * + * This property holds the output size in pixels, taking transform into account. + * + * \sa Output::modeSize + * \sa Output::transform + */ +QSize Output::pixelSize() const +{ + Q_D(const Output); + + switch (d->transform) { + case Output::Transform::Rotated90: + case Output::Transform::Rotated270: + case Output::Transform::Flipped90: + case Output::Transform::Flipped270: + return modeSize().transposed(); + default: + break; + } + + return modeSize(); +} + +/*! + * \property Output::modeSize + * \brief Actual resolution. + * + * This property holds the actual resolution of the output, without + * being multiplied by the scale. + * + * \sa Output::pixelSize + * \sa Output::scale + */ +QSize Output::modeSize() const +{ + Q_D(const Output); + + if (d->currentMode == d->modes.end()) + return QSize(); + return d->currentMode->size; +} + +/*! + * \property Output::scale + * \brief Scale. + * + * This property holds the output scale. + */ +qreal Output::scale() const +{ + Q_D(const Output); + return d->scale; +} + +/*! + * \property Output::geometry + * \brief Geometry of the output. + * + * This property holds the position of the output in the compositor space + * and the size in pixels. + * + * The geometry is transformed according to the output transform. + * + * \sa Output::transform + * \sa Output::globalPosition + * \sa Output::pixelSize + * \sa Output::scale + */ +QRect Output::geometry() const +{ + Q_D(const Output); + + if (d->currentMode == d->modes.end()) + return QRect(); + + auto rect = QRect(d->globalPosition, pixelSize() / d->scale); + auto x = rect.x(); + auto y = rect.y(); + auto width = rect.width(); + auto height = rect.height(); + + switch (d->transform) { + case Output::Transform::Normal: + return rect; + case Output::Transform::Rotated90: + return QRect(y, rect.left(), height, width); + case Output::Transform::Rotated180: + return QRect(rect.topLeft(), QSize(width, height)); + case Output::Transform::Rotated270: + return QRect(rect.top(), x, height, width); + case Output::Transform::Flipped: + return QRect(x + width, y, -width, height); + case Output::Transform::Flipped90: + return QRect(y + height, rect.left(), -height, width); + case Output::Transform::Flipped180: + return QRect(rect.bottomRight(), QSize(-width, -height)); + case Output::Transform::Flipped270: + return QRect(rect.top(), x + width, height, -width); + } +} + +/*! + * \property Output::refreshRate + * \brief The refresh rate of the output in mHz. + * + * This property holds the refresh rate of the output in mHz. + */ +int Output::refreshRate() const +{ + Q_D(const Output); + + if (d->currentMode == d->modes.end()) + return 0; + return d->currentMode->refreshRate; +} + +/*! + * \property Output::depth + * \brief Color depth. + * + * This property holds the color depth of the output. + * It must be compatible with the image format: for example if the + * Output::format property is QImage::Format_RGB32, depth must be 32. + * + * \ sa Output::format + */ +int Output::depth() const +{ + Q_D(const Output); + return d->depth; +} + +/*! + * \property Output::format + * \brief Image format. + * + * This property holds the image format of the output. + * It must be compatible with color depth: for example if the + * Output::depth property is 32, format might be QImage::Format_RGB32. + * + * \sa Output::depth + */ +QImage::Format Output::format() const +{ + Q_D(const Output); + return d->format; +} + +/*! + * \property Output::powerState + * \brief The power state. + * + * This property holds the power state of the screen. + */ +Output::PowerState Output::powerState() const +{ + Q_D(const Output); + return d->powerState; +} + +void Output::setPowerState(Output::PowerState powerState) +{ + Q_D(Output); + + if (powerState == d->powerState) + return; + + d->powerState = powerState; + emit powerStateChanged(powerState); +} + +Output::Subpixel Output::subpixel() const +{ + Q_D(const Output); + return d->subpixel; +} + +Output::Transform Output::transform() const +{ + Q_D(const Output); + return d->transform; +} + +Output::ContentType Output::contentType() const +{ + Q_D(const Output); + return d->contentType; +} + +void Output::setContentType(Output::ContentType contentType) +{ + Q_D(Output); + + if (d->contentType == contentType) + return; + + d->contentType = contentType; + emit contentTypeChanged(contentType); +} + +QDebug operator<<(QDebug debug, const Output *output) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + + if (output) { + debug << output->metaObject()->className() << '(' << static_cast(output); + debug << ", name=" << output->name(); + debug << ", geometry=" << output->geometry(); + // scale + if (debug.verbosity() > 2) { + debug << ", manufacturer=" << output->manufacturer(); + debug << ", model=" << output->model(); + debug << ", serialNumber=" << output->serialNumber(); + } + debug << ')'; + } else { + debug << "Output(0x0)"; + } + + return debug; +} + +bool Output::Mode::operator==(const Output::Mode &m) const +{ + return flags == m.flags && size == m.size && refreshRate == m.refreshRate; +} + +/* + * OutputPrivate + */ + +OutputPrivate::OutputPrivate(Output *self) + : q_ptr(self) +{ +} + +void OutputPrivate::setManufacturer(const QString &manufacturer) +{ + Q_Q(Output); + + if (this->manufacturer != manufacturer) { + this->manufacturer = manufacturer; + emit q->manufacturerChanged(manufacturer); + } +} + +void OutputPrivate::setModel(const QString &model) +{ + Q_Q(Output); + + if (this->model != model) { + this->model = model; + emit q->modelChanged(model); + } +} + +void OutputPrivate::setSubpixel(Output::Subpixel subpixel) +{ + Q_Q(Output); + + if (this->subpixel != subpixel) { + this->subpixel = subpixel; + emit q->subpixelChanged(subpixel); + } +} + +void OutputPrivate::setTransform(Output::Transform transform) +{ + Q_Q(Output); + + if (this->transform != transform) { + this->transform = transform; + emit q->transformChanged(transform); + } +} + +void OutputPrivate::setPhysicalSize(const QSize &physicalSize) +{ + Q_Q(Output); + + if (this->physicalSize != physicalSize) { + this->physicalSize = physicalSize; + emit q->physicalSizeChanged(physicalSize); + } +} + +void OutputPrivate::setGlobalPosition(const QPoint &globalPosition) +{ + Q_Q(Output); + + if (this->globalPosition != globalPosition) { + this->globalPosition = globalPosition; + emit q->globalPositionChanged(globalPosition); + } +} + +void OutputPrivate::setScale(qreal scale) +{ + Q_Q(Output); + + if (this->scale != scale) { + this->scale = scale; + emit q->scaleChanged(scale); + } +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/output.h b/src/platform/output.h new file mode 100644 index 00000000..85b98194 --- /dev/null +++ b/src/platform/output.h @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class OutputPrivate; + +class LIRIAURORAPLATFORM_EXPORT Output : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid uuid READ uuid CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString manufacturer READ manufacturer NOTIFY manufacturerChanged) + Q_PROPERTY(QString model READ model NOTIFY modelChanged) + Q_PROPERTY(QString serialNumber READ serialNumber CONSTANT) + Q_PROPERTY(QSize physicalSize READ physicalSize NOTIFY physicalSizeChanged) + Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged) + Q_PROPERTY(QPoint globalPosition READ globalPosition NOTIFY globalPositionChanged) + Q_PROPERTY(QSize modeSize READ modeSize NOTIFY modeSizeChanged) + Q_PROPERTY(QSize pixelSize READ pixelSize NOTIFY pixelSizeChanged) + Q_PROPERTY(qreal scale READ scale NOTIFY scaleChanged) + Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) + Q_PROPERTY(int refreshRate READ refreshRate NOTIFY refreshRateChanged) + Q_PROPERTY(int depth READ depth CONSTANT) + Q_PROPERTY(int format READ format CONSTANT) + Q_PROPERTY(PowerState powerState READ powerState WRITE setPowerState NOTIFY powerStateChanged) + Q_PROPERTY(Subpixel subpixel READ subpixel NOTIFY subpixelChanged) + Q_PROPERTY(Transform transform READ transform NOTIFY transformChanged) + Q_PROPERTY( + ContentType contenType READ contentType WRITE setContentType NOTIFY contentTypeChanged) + Q_DECLARE_PRIVATE(Output) +public: + Q_DISABLE_COPY_MOVE(Output) + + enum class Capability : uint { + PowerState = 1, + Overscan = 1 << 1, + Vrr = 1 << 2, + RgbRange = 1 << 3, + HighDynamicRange = 1 << 4, + WideColorGamut = 1 << 5, + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + enum class PowerState { + On, + Standby, + Suspend, + Off, + }; + Q_ENUM(PowerState) + + enum class Subpixel { + Unknown, + None, + HorizontalRGB, + HorizontalBGR, + VerticalRGB, + VerticalBGR, + }; + Q_ENUM(Subpixel) + + enum class Transform { + Normal, + Rotated90, + Rotated180, + Rotated270, + Flipped, + Flipped90, + Flipped180, + Flipped270, + }; + Q_ENUM(Transform); + + enum class ContentType { + Unknown, + Photo, + Video, + Game, + }; + Q_ENUM(ContentType) + + struct Mode + { + enum class Flag { + None = 0, + Current = 1 << 0, + Preferred = 1 << 1, + }; + Q_DECLARE_FLAGS(Flags, Flag) + + /*! Weather this mode is current or preferred */ + Flags flags = Flag::None; + + /*! Size in pixel space */ + QSize size; + + /*! Refresh rate in mHz */ + int refreshRate = 0; + + bool operator==(const Mode &m) const; + }; + + explicit Output(QObject *parent = nullptr); + ~Output(); + + QUuid uuid() const; + + virtual QString name() const; + virtual QString description() const; + + QString manufacturer() const; + QString model() const; + QString serialNumber() const; + + QSize physicalSize() const; + + bool isEnabled() const; + + QPoint globalPosition() const; + QSize pixelSize() const; + QSize modeSize() const; + + qreal scale() const; + + QRect geometry() const; + + int refreshRate() const; + + int depth() const; + QImage::Format format() const; + + PowerState powerState() const; + void setPowerState(PowerState powerState); + + Subpixel subpixel() const; + + Transform transform() const; + + ContentType contentType() const; + void setContentType(ContentType contentType); + +signals: + void manufacturerChanged(const QString &manufacturer); + void modelChanged(const QString &model); + void physicalSizeChanged(const QSize &physicalSize); + void enabledChanged(bool enabled); + void globalPositionChanged(const QPoint &globalPosition); + void modeSizeChanged(const QSize &modeSize); + void pixelSizeChanged(const QSize &pixelSize); + void scaleChanged(qreal scale); + void geometryChanged(const QRect &geometry); + void refreshRateChanged(int refreshRate); + void powerStateChanged(Output::PowerState powerState); + void subpixelChanged(Output::Subpixel subpixel); + void transformChanged(Output::Transform transform); + void contentTypeChanged(Output::ContentType contentType); + void modeAdded(const Mode &mode); + void modeChanged(const Mode &mode); + +protected: + QScopedPointer const d_ptr; +}; + +LIRIAURORAPLATFORM_EXPORT QDebug operator<<(QDebug debug, const Output *output); + +typedef QList Outputs; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Output::Capabilities) +Q_DECLARE_OPERATORS_FOR_FLAGS(Output::Mode::Flags) + +} // namespace Platform + +} // namespace Aurora + +Q_DECLARE_METATYPE(Aurora::Platform::Output::PowerState) +Q_DECLARE_METATYPE(Aurora::Platform::Output::Subpixel) +Q_DECLARE_METATYPE(Aurora::Platform::Output::Transform) +Q_DECLARE_METATYPE(Aurora::Platform::Output::ContentType) +Q_DECLARE_METATYPE(Aurora::Platform::Output::Mode) diff --git a/src/platform/output_p.h b/src/platform/output_p.h new file mode 100644 index 00000000..8c61d6b5 --- /dev/null +++ b/src/platform/output_p.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Aurora API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT OutputPrivate +{ + Q_DECLARE_PUBLIC(Output) +public: + explicit OutputPrivate(Output *self); + + static OutputPrivate *get(Output *output) + { + return output->d_func(); + } + + void setManufacturer(const QString &manufacturer); + void setModel(const QString &model); + void setSubpixel(Output::Subpixel subpixel); + void setTransform(Output::Transform transform); + void setPhysicalSize(const QSize &physicalSize); + void setGlobalPosition(const QPoint &globalPosition); + void setScale(qreal scale); + + QUuid uuid; + QString manufacturer; + QString model; + QString serialNumber; + QSize physicalSize; + bool enabled = true; + QPoint globalPosition; + qreal scale = 1.0f; + int depth = 0; + QImage::Format format = QImage::Format_Invalid; + Output::PowerState powerState = Output::PowerState::On; + Output::Subpixel subpixel = Output::Subpixel::Unknown; + Output::Transform transform = Output::Transform::Normal; + Output::ContentType contentType = Output::ContentType::Unknown; + QList modes; + QList::iterator currentMode = modes.end(); + +protected: + Output *q_ptr = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/pointerdevice.cpp b/src/platform/pointerdevice.cpp new file mode 100644 index 00000000..549a2fd2 --- /dev/null +++ b/src/platform/pointerdevice.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "pointerdevice.h" + +namespace Aurora { + +namespace Platform { + +PointerDevice::PointerDevice(QObject *parent) + : InputDevice(parent) +{ +} + +InputDevice::DeviceType PointerDevice::deviceType() +{ + return DeviceType::Pointer; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/pointerdevice.h b/src/platform/pointerdevice.h new file mode 100644 index 00000000..076b00a4 --- /dev/null +++ b/src/platform/pointerdevice.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT PointerDevice : public InputDevice +{ + Q_OBJECT +public: + explicit PointerDevice(QObject *parent = nullptr); + + DeviceType deviceType() override; + +signals: + void motion(const QPointF &absPosition); + void buttonPressed(Qt::MouseButton button); + void buttonReleased(Qt::MouseButton button); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/platform/session.cpp b/src/platform/session.cpp new file mode 100644 index 00000000..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..6e599ba0 --- /dev/null +++ b/src/platform/session.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include + +namespace Aurora { + +namespace Platform { + +class LIRIAURORAPLATFORM_EXPORT Session : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) +public: + Q_DISABLE_COPY_MOVE(Session) + + enum class Type { + Noop, + }; + Q_ENUM(Type) + + enum class Capability : uint { + SwitchTerminal = 0x1, + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + virtual Capabilities capabilities() const = 0; + + virtual bool isActive() const = 0; + + virtual QString seat() const = 0; + virtual uint terminal() const = 0; + + virtual int openRestricted(const QString &fileName) = 0; + virtual void closeRestricted(int fileDescriptor) = 0; + + virtual void switchTo(uint terminal) = 0; + + static Session *create(QObject *parent = nullptr); + +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..7b14917e --- /dev/null +++ b/src/platform/session_noop.cpp @@ -0,0 +1,60 @@ +// 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() +{ +} + +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..b90e6973 --- /dev/null +++ b/src/platform/session_noop_p.h @@ -0,0 +1,50 @@ +// 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; + + 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