From d76100781f34e7fe1324a5a8c7743333c58fae03 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Sun, 4 Feb 2024 04:58:58 -0800 Subject: [PATCH] feat: add layershell support --- .clang-tidy | 2 + CMakeLists.txt | 3 + shell.nix | 4 +- src/cpp/layershell.cpp | 253 ++++++++++++++++++++++++++++++++++++++++ src/cpp/layershell.hpp | 166 ++++++++++++++++++++++++++ src/cpp/proxywindow.cpp | 8 ++ src/cpp/proxywindow.hpp | 23 ++-- src/cpp/qmlglobal.cpp | 59 ++++++++++ src/cpp/qmlglobal.hpp | 34 ++++++ src/cpp/qmlscreen.cpp | 84 +++++++++++++ src/cpp/qmlscreen.hpp | 51 ++++++++ src/cpp/scavenge.cpp | 1 + 12 files changed, 677 insertions(+), 11 deletions(-) create mode 100644 src/cpp/layershell.cpp create mode 100644 src/cpp/layershell.hpp create mode 100644 src/cpp/qmlglobal.cpp create mode 100644 src/cpp/qmlglobal.hpp create mode 100644 src/cpp/qmlscreen.cpp create mode 100644 src/cpp/qmlscreen.hpp diff --git a/.clang-tidy b/.clang-tidy index 92821ad3..b53ebf35 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,6 +11,7 @@ Checks: > -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-non-private-member-variables-in-classes, google-build-using-namespace. google-explicit-constructor, google-global-names-in-headers, @@ -41,6 +42,7 @@ CheckOptions: readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.ConstantCase: UPPER_CASE readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumConstantCase: CamelCase readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.MemberCase: camelBack readability-identifier-naming.NamespaceCase: lower_case diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c115eb6..a56eb229 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ qt_add_executable(qtshell src/cpp/proxywindow.cpp src/cpp/scavenge.cpp src/cpp/rootwrapper.cpp + src/cpp/layershell.cpp + src/cpp/qmlglobal.cpp + src/cpp/qmlscreen.cpp ) qt_add_qml_module(qtshell URI QtShell) diff --git a/shell.nix b/shell.nix index 04f5cd7f..3325fc5d 100644 --- a/shell.nix +++ b/shell.nix @@ -11,8 +11,8 @@ domain = "git.outfoxxed.me"; owner = "outfoxxed"; repo = "layer-shell-qt-nokde"; - rev = "a50d30687cc03ae4da177033faf5f038c3e1a8b2"; - sha256 = "5fNwoCce74SSqR5XB3fJ8GI+D5cbkcLRok42k8R3XSw="; + rev = "2ebe7b313efbacfcd62ec39e2fda6b4c740d0770"; + sha256 = "N/nMwf5LQMMwCJvG7J/6xug/EUppHedQCngzCkH8Auk="; })) {}; #pkgs.callPackage (import /home/admin/programming/outfoxxed/layer-shell-qt) {}; in pkgs.mkShell { nativeBuildInputs = with pkgs; [ diff --git a/src/cpp/layershell.cpp b/src/cpp/layershell.cpp new file mode 100644 index 00000000..4d554a5a --- /dev/null +++ b/src/cpp/layershell.cpp @@ -0,0 +1,253 @@ +#include "layershell.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proxywindow.hpp" +#include "qmlscreen.hpp" + +void ProxyShellWindow::earlyInit(QObject* old) { + ProxyWindowBase::earlyInit(old); + + QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyShellWindow::screenChanged); + + this->shellWindow = LayerShellQt::Window::get(this->window); + + // dont want to steal focus unless actually configured to + this->shellWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityNone); + + // this dosent reset if its unset + this->shellWindow->setExclusiveZone(0); + + // clang-format off + QObject::connect(this->shellWindow, &LayerShellQt::Window::anchorsChanged, this, &ProxyShellWindow::anchorsChanged); + QObject::connect(this->shellWindow, &LayerShellQt::Window::marginsChanged, this, &ProxyShellWindow::marginsChanged); + QObject::connect(this->shellWindow, &LayerShellQt::Window::layerChanged, this, &ProxyShellWindow::layerChanged); + QObject::connect(this->shellWindow, &LayerShellQt::Window::keyboardInteractivityChanged, this, &ProxyShellWindow::keyboardFocusChanged); + // clang-format on +} + +void ProxyShellWindow::componentComplete() { + this->complete = true; + + // The default anchor settings are a hazard because they cover the entire screen. + // We opt for 0 anchors by default to avoid blocking user input. + this->setAnchors(this->stagingAnchors); + + // Make sure we signal changes from anchors, but only if this is a reload. + // If we do it on first load then it sends an extra change at 0px. + if (this->stagingAnchors.mLeft && this->stagingAnchors.mRight && this->width() != 0) + this->widthChanged(this->width()); + if (this->stagingAnchors.mTop && this->stagingAnchors.mBottom && this->height() != 0) + this->heightChanged(this->height()); + + this->window->setVisible(this->stagingVisible); + + ProxyWindowBase::componentComplete(); +} + +QQuickWindow* ProxyShellWindow::disownWindow() { + QObject::disconnect(this->shellWindow, nullptr, this, nullptr); + return ProxyWindowBase::disownWindow(); +} + +void ProxyShellWindow::setVisible(bool visible) { + if (!this->complete) this->stagingVisible = visible; + else ProxyWindowBase::setVisible(visible); +} + +bool ProxyShellWindow::isVisible() { + return this->complete ? ProxyWindowBase::isVisible() : this->stagingVisible; +} + +void ProxyShellWindow::setWidth(qint32 width) { + this->requestedWidth = width; + + // only update the actual size if not blocked by anchors + auto anchors = this->anchors(); + if (this->complete && (!anchors.mLeft || !anchors.mRight)) ProxyWindowBase::setWidth(width); +} + +qint32 ProxyShellWindow::width() { + return this->complete ? ProxyWindowBase::width() : this->requestedWidth; +} + +void ProxyShellWindow::setHeight(qint32 height) { + this->requestedHeight = height; + + // only update the actual size if not blocked by anchors + auto anchors = this->anchors(); + if (this->complete && (!anchors.mTop || !anchors.mBottom)) ProxyWindowBase::setHeight(height); +} + +qint32 ProxyShellWindow::height() { + return this->complete ? ProxyWindowBase::height() : this->requestedHeight; +} + +void ProxyShellWindow::setScreen(QtShellScreenInfo* screen) { + this->window->setScreen(screen->screen); +} + +QtShellScreenInfo* ProxyShellWindow::screen() const { + return new QtShellScreenInfo( + const_cast(this), // NOLINT + this->window->screen() + ); +} + +void ProxyShellWindow::setAnchors(Anchors anchors) { + if (!this->complete) { + this->stagingAnchors = anchors; + return; + } + + auto lsAnchors = LayerShellQt::Window::Anchors(); + if (anchors.mLeft) lsAnchors |= LayerShellQt::Window::AnchorLeft; + if (anchors.mRight) lsAnchors |= LayerShellQt::Window::AnchorRight; + if (anchors.mTop) lsAnchors |= LayerShellQt::Window::AnchorTop; + if (anchors.mBottom) lsAnchors |= LayerShellQt::Window::AnchorBottom; + + if (!anchors.mLeft || !anchors.mRight) ProxyWindowBase::setWidth(this->requestedWidth); + if (!anchors.mTop || !anchors.mBottom) ProxyWindowBase::setHeight(this->requestedHeight); + + this->shellWindow->setAnchors(lsAnchors); +} + +Anchors ProxyShellWindow::anchors() const { + if (!this->complete) return this->stagingAnchors; + + auto lsAnchors = this->shellWindow->anchors(); + + Anchors anchors; + anchors.mLeft = lsAnchors.testFlag(LayerShellQt::Window::AnchorLeft); + anchors.mRight = lsAnchors.testFlag(LayerShellQt::Window::AnchorRight); + anchors.mTop = lsAnchors.testFlag(LayerShellQt::Window::AnchorTop); + anchors.mBottom = lsAnchors.testFlag(LayerShellQt::Window::AnchorBottom); + + return anchors; +} + +void ProxyShellWindow::setExclusiveZone(qint32 zone) { this->shellWindow->setExclusiveZone(zone); } + +qint32 ProxyShellWindow::exclusiveZone() const { return this->shellWindow->exclusionZone(); } + +void ProxyShellWindow::setMargins(Margins margins) { + auto lsMargins = QMargins(margins.mLeft, margins.mTop, margins.mRight, margins.mBottom); + this->shellWindow->setMargins(lsMargins); +} + +Margins ProxyShellWindow::margins() const { + auto lsMargins = this->shellWindow->margins(); + + auto margins = Margins(); + margins.mLeft = lsMargins.left(); + margins.mRight = lsMargins.right(); + margins.mTop = lsMargins.top(); + margins.mBottom = lsMargins.bottom(); + + return margins; +} + +void ProxyShellWindow::setLayer(Layer::Enum layer) { + auto lsLayer = LayerShellQt::Window::LayerBackground; + + // clang-format off + switch (layer) { + case Layer::Background: lsLayer = LayerShellQt::Window::LayerBackground; break; + case Layer::Bottom: lsLayer = LayerShellQt::Window::LayerBottom; break; + case Layer::Top: lsLayer = LayerShellQt::Window::LayerTop; break; + case Layer::Overlay: lsLayer = LayerShellQt::Window::LayerOverlay; break; + } + // clang-format on + + this->shellWindow->setLayer(lsLayer); +} + +Layer::Enum ProxyShellWindow::layer() const { + auto layer = Layer::Top; + auto lsLayer = this->shellWindow->layer(); + + // clang-format off + switch (lsLayer) { + case LayerShellQt::Window::LayerBackground: layer = Layer::Background; break; + case LayerShellQt::Window::LayerBottom: layer = Layer::Bottom; break; + case LayerShellQt::Window::LayerTop: layer = Layer::Top; break; + case LayerShellQt::Window::LayerOverlay: layer = Layer::Overlay; break; + } + // clang-format on + + return layer; +} + +void ProxyShellWindow::setScope(const QString& scope) { this->shellWindow->setScope(scope); } + +QString ProxyShellWindow::scope() const { return this->shellWindow->scope(); } + +void ProxyShellWindow::setKeyboardFocus(KeyboardFocus::Enum focus) { + auto lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; + + // clang-format off + switch (focus) { + case KeyboardFocus::None: lsFocus = LayerShellQt::Window::KeyboardInteractivityNone; break; + case KeyboardFocus::Exclusive: lsFocus = LayerShellQt::Window::KeyboardInteractivityExclusive; break; + case KeyboardFocus::OnDemand: lsFocus = LayerShellQt::Window::KeyboardInteractivityOnDemand; break; + } + // clang-format on + + this->shellWindow->setKeyboardInteractivity(lsFocus); +} + +KeyboardFocus::Enum ProxyShellWindow::keyboardFocus() const { + auto focus = KeyboardFocus::None; + auto lsFocus = this->shellWindow->keyboardInteractivity(); + + // clang-format off + switch (lsFocus) { + case LayerShellQt::Window::KeyboardInteractivityNone: focus = KeyboardFocus::None; break; + case LayerShellQt::Window::KeyboardInteractivityExclusive: focus = KeyboardFocus::Exclusive; break; + case LayerShellQt::Window::KeyboardInteractivityOnDemand: focus = KeyboardFocus::OnDemand; break; + } + // clang-format on + + return focus; +} + +void ProxyShellWindow::setScreenConfiguration(ScreenConfiguration::Enum configuration) { + auto lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; + + // clang-format off + switch (configuration) { + case ScreenConfiguration::Window: lsConfiguration = LayerShellQt::Window::ScreenFromQWindow; break; + case ScreenConfiguration::Compositor: lsConfiguration = LayerShellQt::Window::ScreenFromCompositor; break; + } + // clang-format on + + this->shellWindow->setScreenConfiguration(lsConfiguration); +} + +ScreenConfiguration::Enum ProxyShellWindow::screenConfiguration() const { + auto configuration = ScreenConfiguration::Window; + auto lsConfiguration = this->shellWindow->screenConfiguration(); + + // clang-format off + switch (lsConfiguration) { + case LayerShellQt::Window::ScreenFromQWindow: configuration = ScreenConfiguration::Window; break; + case LayerShellQt::Window::ScreenFromCompositor: configuration = ScreenConfiguration::Compositor; break; + } + // clang-format on + + return configuration; +} + +void ProxyShellWindow::setCloseOnDismissed(bool close) { + this->shellWindow->setCloseOnDismissed(close); +} + +bool ProxyShellWindow::closeOnDismissed() const { return this->shellWindow->closeOnDismissed(); } diff --git a/src/cpp/layershell.hpp b/src/cpp/layershell.hpp new file mode 100644 index 00000000..4155a1ea --- /dev/null +++ b/src/cpp/layershell.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proxywindow.hpp" +#include "qmlscreen.hpp" + +class Anchors { + Q_GADGET; + Q_PROPERTY(bool left MEMBER mLeft); + Q_PROPERTY(bool right MEMBER mRight); + Q_PROPERTY(bool top MEMBER mTop); + Q_PROPERTY(bool bottom MEMBER mBottom); + +public: + bool mLeft = false; + bool mRight = false; + bool mTop = false; + bool mBottom = false; +}; + +class Margins { + Q_GADGET; + Q_PROPERTY(qint32 left MEMBER mLeft); + Q_PROPERTY(qint32 right MEMBER mRight); + Q_PROPERTY(qint32 top MEMBER mTop); + Q_PROPERTY(qint32 bottom MEMBER mBottom); + +public: + qint32 mLeft = 0; + qint32 mRight = 0; + qint32 mTop = 0; + qint32 mBottom = 0; +}; + +namespace Layer { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum { + Background = 0, + Bottom = 1, + Top = 2, + Overlay = 3, +}; +Q_ENUM_NS(Enum); + +} // namespace Layer + +namespace KeyboardFocus { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum { + None = 0, + Exclusive = 1, + OnDemand = 2, +}; +Q_ENUM_NS(Enum); + +} // namespace KeyboardFocus + +namespace ScreenConfiguration { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum { + Window = 0, + Compositor = 1, +}; +Q_ENUM_NS(Enum); + +} // namespace ScreenConfiguration + +class ProxyShellWindow: public ProxyWindowBase { + // clang-format off + Q_OBJECT; + Q_PROPERTY(QtShellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged); + Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged); + Q_PROPERTY(qint32 exclusionZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusionZoneChanged) + Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged) + Q_PROPERTY(Layer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged) + Q_PROPERTY(QString scope READ scope WRITE setScope) + Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged) + Q_PROPERTY(ScreenConfiguration::Enum screenConfiguration READ screenConfiguration WRITE setScreenConfiguration) + Q_PROPERTY(bool closeOnDismissed READ closeOnDismissed WRITE setCloseOnDismissed); + Q_CLASSINFO("DefaultProperty", "data"); + QML_ELEMENT; + // clang-format on + +protected: + void earlyInit(QObject* old) override; + +public: + void componentComplete() override; + QQuickWindow* disownWindow() override; + + QQmlListProperty data(); + + void setVisible(bool visible) override; + bool isVisible() override; + + void setWidth(qint32 width) override; + qint32 width() override; + + void setHeight(qint32 height) override; + qint32 height() override; + + void setScreen(QtShellScreenInfo* screen); + [[nodiscard]] QtShellScreenInfo* screen() const; + + void setAnchors(Anchors anchors); + [[nodiscard]] Anchors anchors() const; + + void setExclusiveZone(qint32 zone); + [[nodiscard]] qint32 exclusiveZone() const; + + void setMargins(Margins margins); + [[nodiscard]] Margins margins() const; + + void setLayer(Layer::Enum layer); + [[nodiscard]] Layer::Enum layer() const; + + void setScope(const QString& scope); + [[nodiscard]] QString scope() const; + + void setKeyboardFocus(KeyboardFocus::Enum focus); + [[nodiscard]] KeyboardFocus::Enum keyboardFocus() const; + + void setScreenConfiguration(ScreenConfiguration::Enum configuration); + [[nodiscard]] ScreenConfiguration::Enum screenConfiguration() const; + + void setCloseOnDismissed(bool close); + [[nodiscard]] bool closeOnDismissed() const; + +signals: + void screenChanged(); + void anchorsChanged(); + void marginsChanged(); + void exclusionZoneChanged(); + void layerChanged(); + void keyboardFocusChanged(); + +private: + LayerShellQt::Window* shellWindow = nullptr; + bool anchorsInitialized = false; + + // needed to ensure size dosent fuck up when changing layershell attachments + // along with setWidth and setHeight overrides + qint32 requestedWidth = 100; + qint32 requestedHeight = 100; + + // width/height must be set before anchors, so we have to track anchors and apply them late + bool complete = false; + bool stagingVisible = false; + Anchors stagingAnchors; +}; diff --git a/src/cpp/proxywindow.cpp b/src/cpp/proxywindow.cpp index f9dc1920..f202f171 100644 --- a/src/cpp/proxywindow.cpp +++ b/src/cpp/proxywindow.cpp @@ -20,9 +20,17 @@ void ProxyWindowBase::earlyInit(QObject* old) { } else { this->window = oldpw->disownWindow(); } + + // clang-format off + QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged); + QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyWindowBase::widthChanged); + QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged); + // clang-format on } QQuickWindow* ProxyWindowBase::disownWindow() { + QObject::disconnect(this->window, nullptr, this, nullptr); + auto data = this->data(); ProxyWindowBase::dataClear(&data); data.clear(&data); diff --git a/src/cpp/proxywindow.hpp b/src/cpp/proxywindow.hpp index 2526f6cd..8bdc8743 100644 --- a/src/cpp/proxywindow.hpp +++ b/src/cpp/proxywindow.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -20,15 +21,16 @@ class ProxyWindowBase: public Scavenger { Q_OBJECT; Q_PROPERTY(QQuickItem* item READ item CONSTANT); - Q_PROPERTY(bool visible READ isVisible WRITE setVisible); - Q_PROPERTY(qint32 width READ width WRITE setWidth); - Q_PROPERTY(qint32 height READ height WRITE setHeight); + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged); + Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged); + Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged); Q_PROPERTY(QColor color READ color WRITE setColor); Q_PROPERTY(QQmlListProperty data READ data); Q_CLASSINFO("DefaultProperty", "data"); protected: void earlyInit(QObject* old) override; + QQuickWindow* window = nullptr; public: explicit ProxyWindowBase(QObject* parent = nullptr): Scavenger(parent) {} @@ -40,17 +42,17 @@ public: void operator=(ProxyWindowBase&&) = delete; // Disown the backing window and delete all its children. - QQuickWindow* disownWindow(); + virtual QQuickWindow* disownWindow(); QQuickItem* item(); - bool isVisible(); + virtual bool isVisible(); virtual void setVisible(bool value); - qint32 width(); + virtual qint32 width(); virtual void setWidth(qint32 value); - qint32 height(); + virtual qint32 height(); virtual void setHeight(qint32 value); QColor color(); @@ -58,6 +60,11 @@ public: QQmlListProperty data(); +signals: + void visibleChanged(bool visible); + void widthChanged(qint32 width); + void heightChanged(qint32 width); + private: static QQmlListProperty dataBacker(QQmlListProperty* prop); static void dataAppend(QQmlListProperty* prop, QObject* obj); @@ -66,8 +73,6 @@ private: static void dataClear(QQmlListProperty* prop); static void dataReplace(QQmlListProperty* prop, qsizetype i, QObject* obj); static void dataRemoveLast(QQmlListProperty* prop); - - QQuickWindow* window = nullptr; }; // qt attempts to resize the window but fails because wayland diff --git a/src/cpp/qmlglobal.cpp b/src/cpp/qmlglobal.cpp new file mode 100644 index 00000000..8b369778 --- /dev/null +++ b/src/cpp/qmlglobal.cpp @@ -0,0 +1,59 @@ +#include "qmlglobal.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "qmlscreen.hpp" + +QtShellGlobal::QtShellGlobal(QObject* parent): QObject(parent) { + auto* app = QCoreApplication::instance(); + auto* guiApp = qobject_cast(app); + + if (guiApp != nullptr) { + // clang-format off + QObject::connect(guiApp, &QGuiApplication::primaryScreenChanged, this, &QtShellGlobal::updateScreens); + QObject::connect(guiApp, &QGuiApplication::screenAdded, this, &QtShellGlobal::updateScreens); + QObject::connect(guiApp, &QGuiApplication::screenRemoved, this, &QtShellGlobal::updateScreens); + // clang-format on + + this->updateScreens(); + } +} + +qsizetype QtShellGlobal::screensCount(QQmlListProperty* prop) { + return static_cast(prop->object)->mScreens.size(); // NOLINT +} + +QtShellScreenInfo* QtShellGlobal::screenAt(QQmlListProperty* prop, qsizetype i) { + return static_cast(prop->object)->mScreens.at(i); // NOLINT +} + +QQmlListProperty QtShellGlobal::screens() { + return QQmlListProperty( + this, + nullptr, + &QtShellGlobal::screensCount, + &QtShellGlobal::screenAt + ); +} + +void QtShellGlobal::updateScreens() { + auto screens = QGuiApplication::screens(); + this->mScreens.resize(screens.size()); + + for (auto i = 0; i < screens.size(); i++) { + if (this->mScreens[i] != nullptr) { + this->mScreens[i]->screen = nullptr; + this->mScreens[i]->setParent(nullptr); // delete if not owned by the js engine + } + + this->mScreens[i] = new QtShellScreenInfo(this, screens[i]); + } + + emit this->screensChanged(); +} diff --git a/src/cpp/qmlglobal.hpp b/src/cpp/qmlglobal.hpp new file mode 100644 index 00000000..55ed4d78 --- /dev/null +++ b/src/cpp/qmlglobal.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "qmlscreen.hpp" + +class QtShellGlobal: public QObject { + Q_OBJECT; + Q_PROPERTY(QQmlListProperty screens READ screens NOTIFY screensChanged); + QML_SINGLETON; + QML_ELEMENT; + +public: + QtShellGlobal(QObject* parent = nullptr); + + QQmlListProperty screens(); + +signals: + void screensChanged(); + +public slots: + void updateScreens(); + +private: + static qsizetype screensCount(QQmlListProperty* prop); + static QtShellScreenInfo* screenAt(QQmlListProperty* prop, qsizetype i); + + QVector mScreens; +}; diff --git a/src/cpp/qmlscreen.cpp b/src/cpp/qmlscreen.cpp new file mode 100644 index 00000000..42e71e45 --- /dev/null +++ b/src/cpp/qmlscreen.cpp @@ -0,0 +1,84 @@ +#include "qmlscreen.hpp" + +#include +#include +#include +#include + +bool QtShellScreenInfo::operator==(QtShellScreenInfo& other) const { + return this->screen == other.screen; +} + +void warnNull() { qWarning() << "attempted to use dangling screen object"; } + +QString QtShellScreenInfo::name() const { + if (this->screen == nullptr) { + warnNull(); + return "{ DANGLING SCREEN POINTER }"; + } + + return this->screen->name(); +} + +qint32 QtShellScreenInfo::width() const { + if (this->screen == nullptr) { + warnNull(); + return 0; + } + + return this->screen->size().width(); +} + +qint32 QtShellScreenInfo::height() const { + if (this->screen == nullptr) { + warnNull(); + return 0; + } + + return this->screen->size().height(); +} + +qreal QtShellScreenInfo::pixelDensity() const { + if (this->screen == nullptr) { + warnNull(); + return 0.0; + } + + return this->screen->physicalDotsPerInch() / 25.4; +} + +qreal QtShellScreenInfo::logicalPixelDensity() const { + if (this->screen == nullptr) { + warnNull(); + return 0.0; + } + + return this->screen->logicalDotsPerInch() / 25.4; +} + +qreal QtShellScreenInfo::devicePixelRatio() const { + if (this->screen == nullptr) { + warnNull(); + return 0.0; + } + + return this->screen->devicePixelRatio(); +} + +Qt::ScreenOrientation QtShellScreenInfo::orientation() const { + if (this->screen == nullptr) { + warnNull(); + return Qt::PrimaryOrientation; + } + + return this->screen->orientation(); +} + +Qt::ScreenOrientation QtShellScreenInfo::primaryOrientation() const { + if (this->screen == nullptr) { + warnNull(); + return Qt::PrimaryOrientation; + } + + return this->screen->primaryOrientation(); +} diff --git a/src/cpp/qmlscreen.hpp b/src/cpp/qmlscreen.hpp new file mode 100644 index 00000000..6cf1b924 --- /dev/null +++ b/src/cpp/qmlscreen.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// unfortunately QQuickScreenInfo is private. +class QtShellScreenInfo: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE("QtShellScreenInfo can only be obtained via QtShell.screens"); + // clang-format off + Q_PROPERTY(QString name READ name NOTIFY nameChanged); + Q_PROPERTY(qint32 width READ width NOTIFY widthChanged); + Q_PROPERTY(qint32 height READ height NOTIFY heightChanged); + Q_PROPERTY(qreal pixelDensity READ pixelDensity NOTIFY logicalPixelDensityChanged); + Q_PROPERTY(qreal logicalPixelDensity READ logicalPixelDensity NOTIFY logicalPixelDensityChanged); + Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY devicePixelRatioChanged); + Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged); + Q_PROPERTY(Qt::ScreenOrientation primatyOrientation READ primaryOrientation NOTIFY primaryOrientationChanged); + // clang-format on + +public: + QtShellScreenInfo(QObject* parent, QScreen* screen): QObject(parent), screen(screen) {} + + bool operator==(QtShellScreenInfo& other) const; + + [[nodiscard]] QString name() const; + [[nodiscard]] qint32 width() const; + [[nodiscard]] qint32 height() const; + [[nodiscard]] qreal pixelDensity() const; + [[nodiscard]] qreal logicalPixelDensity() const; + [[nodiscard]] qreal devicePixelRatio() const; + [[nodiscard]] Qt::ScreenOrientation orientation() const; + [[nodiscard]] Qt::ScreenOrientation primaryOrientation() const; + + QScreen* screen; + +signals: + void nameChanged(); + void widthChanged(); + void heightChanged(); + void pixelDensityChanged(); + void logicalPixelDensityChanged(); + void devicePixelRatioChanged(); + void orientationChanged(); + void primaryOrientationChanged(); +}; diff --git a/src/cpp/scavenge.cpp b/src/cpp/scavenge.cpp index 50c1f8f4..81f4851d 100644 --- a/src/cpp/scavenge.cpp +++ b/src/cpp/scavenge.cpp @@ -1,6 +1,7 @@ #include "scavenge.hpp" #include +#include #include #include #include