1
0
Fork 0

Compare commits

...

10 commits

36 changed files with 1014 additions and 232 deletions

View file

@ -37,6 +37,7 @@ qt_add_library(quickshell-core STATIC
common.cpp
iconprovider.cpp
scriptmodel.cpp
colorquantizer.cpp
)
qt_add_qml_module(quickshell-core

View file

@ -6,8 +6,6 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "util.hpp"
SystemClock::SystemClock(QObject* parent): QObject(parent) {
QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::onTimeout);
this->update();
@ -48,19 +46,16 @@ void SystemClock::update() {
void SystemClock::setTime(const QDateTime& targetTime) {
auto currentTime = QDateTime::currentDateTime();
auto offset = currentTime.msecsTo(targetTime);
auto dtime = offset > -500 && offset < 500 ? targetTime : currentTime;
auto time = dtime.time();
this->currentTime = offset > -500 && offset < 500 ? targetTime : currentTime;
auto secondPrecision = this->mPrecision >= SystemClock::Seconds;
auto secondChanged = this->setSeconds(secondPrecision ? time.second() : 0);
auto time = this->currentTime.time();
this->currentTime.setTime(QTime(
this->mPrecision >= SystemClock::Hours ? time.hour() : 0,
this->mPrecision >= SystemClock::Minutes ? time.minute() : 0,
this->mPrecision >= SystemClock::Seconds ? time.second() : 0
));
auto minutePrecision = this->mPrecision >= SystemClock::Minutes;
auto minuteChanged = this->setMinutes(minutePrecision ? time.minute() : 0);
auto hourPrecision = this->mPrecision >= SystemClock::Hours;
auto hourChanged = this->setHours(hourPrecision ? time.hour() : 0);
DropEmitter::call(secondChanged, minuteChanged, hourChanged);
emit this->dateChanged();
}
void SystemClock::schedule(const QDateTime& targetTime) {
@ -76,11 +71,11 @@ void SystemClock::schedule(const QDateTime& targetTime) {
auto nextTime = offset > 0 && offset < 500 ? targetTime : currentTime;
auto baseTimeT = nextTime.time();
nextTime.setTime(
{hourPrecision ? baseTimeT.hour() : 0,
minutePrecision ? baseTimeT.minute() : 0,
secondPrecision ? baseTimeT.second() : 0}
);
nextTime.setTime(QTime(
hourPrecision ? baseTimeT.hour() : 0,
minutePrecision ? baseTimeT.minute() : 0,
secondPrecision ? baseTimeT.second() : 0
));
if (secondPrecision) nextTime = nextTime.addSecs(1);
else if (minutePrecision) nextTime = nextTime.addSecs(60);
@ -91,7 +86,3 @@ void SystemClock::schedule(const QDateTime& targetTime) {
this->timer.start(static_cast<qint32>(delay));
this->targetTime = nextTime;
}
DEFINE_MEMBER_GETSET(SystemClock, hours, setHours);
DEFINE_MEMBER_GETSET(SystemClock, minutes, setMinutes);
DEFINE_MEMBER_GETSET(SystemClock, seconds, setSeconds);

View file

@ -7,9 +7,26 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "util.hpp"
///! System clock accessor.
/// SystemClock is a view into the system's clock.
/// It updates at hour, minute, or second intervals depending on @@precision.
///
/// # Examples
/// ```qml
/// SystemClock {
/// id: clock
/// precision: SystemClock.Seconds
/// }
///
/// @@QtQuick.Text {
/// text: Qt.formatDateTime(clock.date, "hh:mm:ss - yyyy-MM-dd")
/// }
/// ```
///
/// > [!WARNING] Clock updates will trigger within 50ms of the system clock changing,
/// > however this can be either before or after the clock changes (+-50ms). If you
/// > need a date object, use @@date instead of constructing a new one, or the time
/// > of the constructed object could be off by up to a second.
class SystemClock: public QObject {
Q_OBJECT;
/// If the clock should update. Defaults to true.
@ -18,12 +35,17 @@ class SystemClock: public QObject {
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
/// The precision the clock should measure at. Defaults to `SystemClock.Seconds`.
Q_PROPERTY(SystemClock::Enum precision READ precision WRITE setPrecision NOTIFY precisionChanged);
/// The current date and time.
///
/// > [!TIP] You can use @@QtQml.Qt.formatDateTime() to get the time as a string in
/// > your format of choice.
Q_PROPERTY(QDateTime date READ date NOTIFY dateChanged);
/// The current hour.
Q_PROPERTY(quint32 hours READ hours NOTIFY hoursChanged);
Q_PROPERTY(quint32 hours READ hours NOTIFY dateChanged);
/// The current minute, or 0 if @@precision is `SystemClock.Hours`.
Q_PROPERTY(quint32 minutes READ minutes NOTIFY minutesChanged);
Q_PROPERTY(quint32 minutes READ minutes NOTIFY dateChanged);
/// The current second, or 0 if @@precision is `SystemClock.Hours` or `SystemClock.Minutes`.
Q_PROPERTY(quint32 seconds READ seconds NOTIFY secondsChanged);
Q_PROPERTY(quint32 seconds READ seconds NOTIFY dateChanged);
QML_ELEMENT;
public:
@ -43,12 +65,15 @@ public:
[[nodiscard]] SystemClock::Enum precision() const;
void setPrecision(SystemClock::Enum precision);
[[nodiscard]] QDateTime date() const { return this->currentTime; }
[[nodiscard]] quint32 hours() const { return this->currentTime.time().hour(); }
[[nodiscard]] quint32 minutes() const { return this->currentTime.time().minute(); }
[[nodiscard]] quint32 seconds() const { return this->currentTime.time().second(); }
signals:
void enabledChanged();
void precisionChanged();
void hoursChanged();
void minutesChanged();
void secondsChanged();
void dateChanged();
private slots:
void onTimeout();
@ -56,17 +81,11 @@ private slots:
private:
bool mEnabled = true;
SystemClock::Enum mPrecision = SystemClock::Seconds;
quint32 mHours = 0;
quint32 mMinutes = 0;
quint32 mSeconds = 0;
QTimer timer;
QDateTime currentTime;
QDateTime targetTime;
void update();
void setTime(const QDateTime& targetTime);
void schedule(const QDateTime& targetTime);
DECLARE_PRIVATE_MEMBER(SystemClock, hours, setHours, mHours, hoursChanged);
DECLARE_PRIVATE_MEMBER(SystemClock, minutes, setMinutes, mMinutes, minutesChanged);
DECLARE_PRIVATE_MEMBER(SystemClock, seconds, setSeconds, mSeconds, secondsChanged);
};

182
src/core/colorquantizer.cpp Normal file
View file

@ -0,0 +1,182 @@
#include "colorquantizer.hpp"
#include <qcolor.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qthreadpool.h>
#include <qtypes.h>
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
: source(source)
, maxDepth(depth)
, rescaleSize(rescaleSize) {
mColors = QList<QColor>();
}
void ColorQuantizerOperation::run() {
quantizeImage();
emit done(mColors);
}
void ColorQuantizerOperation::quantizeImage() {
mColors.clear();
if (source->isEmpty()) {
return;
}
QImage image(source->toLocalFile());
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
image = image.scaled(
static_cast<int>(rescaleSize),
static_cast<int>(rescaleSize),
Qt::KeepAspectRatio,
Qt::SmoothTransformation
);
}
if (image.isNull()) {
qWarning() << "Failed to load image from" << source;
return;
}
QList<QColor> pixels;
for (int y = 0; y < image.height(); ++y) {
for (int x = 0; x < image.width(); ++x) {
QRgb pixel = image.pixel(x, y);
if (qAlpha(pixel) == 0) {
continue;
}
pixels.append(QColor::fromRgb(pixel));
}
}
QDateTime startTime = QDateTime::currentDateTime();
mColors = quantization(pixels, 0);
QDateTime endTime = QDateTime::currentDateTime();
qint64 milliseconds = startTime.msecsTo(endTime);
qDebug() << "Color Quantization took: " << milliseconds << "ms";
}
QList<QColor> ColorQuantizerOperation::quantization(QList<QColor>& rgbValues, qreal depth) {
if (depth >= maxDepth || rgbValues.isEmpty()) {
if (rgbValues.isEmpty()) {
return QList<QColor>();
}
int totalR = 0;
int totalG = 0;
int totalB = 0;
for (const QColor& color: rgbValues) {
totalR += color.red();
totalG += color.green();
totalB += color.blue();
}
QColor avgColor(
qRound(totalR / static_cast<double>(rgbValues.size())),
qRound(totalG / static_cast<double>(rgbValues.size())),
qRound(totalB / static_cast<double>(rgbValues.size()))
);
return QList<QColor>() << avgColor;
}
QString dominantChannel = findBiggestColorRange(rgbValues);
std::ranges::sort(rgbValues, [dominantChannel](const QColor& a, const QColor& b) {
if (dominantChannel == "r") return a.red() < b.red();
else if (dominantChannel == "g") return a.green() < b.green();
return a.blue() < b.blue();
});
qsizetype mid = rgbValues.size() / 2;
QList<QColor> leftHalf = rgbValues.mid(0, mid);
QList<QColor> rightHalf = rgbValues.mid(mid + 1);
QList<QColor> result;
result.append(quantization(leftHalf, depth + 1));
result.append(quantization(rightHalf, depth + 1));
return result;
}
QString ColorQuantizerOperation::findBiggestColorRange(const QList<QColor>& rgbValues) {
if (rgbValues.isEmpty()) return "r";
int rMin = 255;
int gMin = 255;
int bMin = 255;
int rMax = 0;
int gMax = 0;
int bMax = 0;
for (const QColor& color: rgbValues) {
rMin = qMin(rMin, color.red());
gMin = qMin(gMin, color.green());
bMin = qMin(bMin, color.blue());
rMax = qMax(rMax, color.red());
gMax = qMax(gMax, color.green());
bMax = qMax(bMax, color.blue());
}
int rRange = rMax - rMin;
int gRange = gMax - gMin;
int bRange = bMax - bMin;
int biggestRange = qMax(rRange, qMax(gRange, bRange));
if (biggestRange == rRange) {
return "r";
} else if (biggestRange == gRange) {
return "g";
} else {
return "b";
}
}
QList<QColor> ColorQuantizer::colors() { return mColors; }
void ColorQuantizer::setSource(const QUrl& source) {
if (mSource != source) {
mSource = source;
emit sourceChanged();
quantizeAsync();
}
}
void ColorQuantizer::setDepth(qreal depth) {
if (mDepth != depth) {
mDepth = depth;
emit depthChanged();
}
}
void ColorQuantizer::setRescaleSize(int rescaleSize) {
if (mRescaleSize != rescaleSize) {
mRescaleSize = rescaleSize;
emit rescaleSizeChanged();
}
}
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
mColors = result;
emit colorsChanged();
isProcessing = false;
}
void ColorQuantizer::quantizeAsync() {
if (isProcessing) return;
qDebug() << "Starting color quantization asynchronously";
isProcessing = true;
auto* task = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
QObject::connect(task, &ColorQuantizerOperation::done, this, &ColorQuantizer::operationFinished);
QThreadPool::globalInstance()->start(task);
}

View file

@ -0,0 +1,95 @@
#pragma once
#include <qlist.h>
#include <qmutex.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qrunnable.h>
#include <qtmetamacros.h>
#include <qtypes.h>
class ColorQuantizerOperation
: public QObject
, public QRunnable {
Q_OBJECT;
public:
ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
void run() override;
signals:
void done(QList<QColor> colors);
private:
QList<QColor> mColors;
QUrl* source;
qreal maxDepth;
qreal rescaleSize;
void quantizeImage();
QList<QColor> quantization(QList<QColor>& rgbValues, qreal depth);
static QString findBiggestColorRange(const QList<QColor>& rgbValues);
};
///! Color Quantization Utility
/// A color quantization utility used for getting prevalent colors in an image, by
/// averaging out the image's color data recursively.
///
/// #### Example
/// ```qml
/// ColorQuantizer {
/// id: colorQuantizer
/// source: Qt.resolvedUrl("./yourImage.png")
/// depth: 3 // Will produce 8 colors (2³)
/// rescaleSize: 64 // Rescale to 64x64 for faster processing
/// }
/// ```
class ColorQuantizer: public QObject {
Q_OBJECT;
/// Access the colors resulting from the color quantization performed.
/// > [!NOTE] The amount of colors returned from the quantization is determined by
/// > the property depth, specifically 2ⁿ where n is the depth.
Q_PROPERTY(QList<QColor> colors READ colors NOTIFY colorsChanged);
/// Path to the image you'd like to run the color quantization on.
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
/// Max depth for the color quantization. Each level of depth represents another
/// binary split of the color space
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
/// > reccommended to rescale, otherwise the quantization process will take much longer.
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
QML_ELEMENT;
public:
QList<QColor> colors();
[[nodiscard]] QUrl source() const { return mSource; }
void setSource(const QUrl& source);
[[nodiscard]] qreal depth() const { return mDepth; }
void setDepth(qreal depth);
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
void setRescaleSize(int rescaleSize);
signals:
void colorsChanged();
void sourceChanged();
void depthChanged();
void rescaleSizeChanged();
public slots:
void operationFinished(const QList<QColor>& result);
private:
bool isProcessing = false;
QList<QColor> mColors;
QUrl mSource;
qreal mDepth = 0;
qreal mRescaleSize = 0;
void quantizeAsync();
};

View file

@ -213,7 +213,7 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
currentArgument += c;
escape = 0;
} else if (c == u'"') {
} else if (c == u'"' || c == u'\'') {
parsingString = false;
} else {
currentArgument += c;
@ -229,7 +229,7 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
percent = false;
} else if (c == '%') {
percent = true;
} else if (c == u'"') {
} else if (c == u'"' || c == u'\'') {
parsingString = true;
} else if (c == u' ') {
if (!currentArgument.isEmpty()) {

View file

@ -1,5 +1,6 @@
#include "imageprovider.hpp"
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qimage.h>
#include <qlogging.h>
@ -7,10 +8,14 @@
#include <qobject.h>
#include <qpixmap.h>
#include <qqmlengine.h>
#include <qtypes.h>
namespace {
namespace {
QMap<QString, QsImageHandle*> liveImages; // NOLINT
quint32 handleIndex = 0; // NOLINT
} // namespace
void parseReq(const QString& req, QString& target, QString& param) {
auto splitIdx = req.indexOf('/');
@ -24,14 +29,9 @@ void parseReq(const QString& req, QString& target, QString& param) {
} // namespace
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent)
: QObject(parent)
, type(type) {
{
auto dbg = QDebug(&this->id);
dbg.nospace() << static_cast<void*>(this);
}
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type)
: type(type)
, id(QString::number(++handleIndex)) {
liveImages.insert(this->id, this);
}
@ -85,3 +85,9 @@ QsPixmapProvider::requestPixmap(const QString& id, QSize* size, const QSize& req
return QPixmap();
}
}
QString QsIndexedImageHandle::url() const {
return this->QsImageHandle::url() % '/' % QString::number(this->changeIndex);
}
void QsIndexedImageHandle::imageChanged() { ++this->changeIndex; }

View file

@ -20,15 +20,13 @@ public:
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
};
class QsImageHandle: public QObject {
Q_OBJECT;
class QsImageHandle {
public:
explicit QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent = nullptr);
~QsImageHandle() override;
explicit QsImageHandle(QQmlImageProviderBase::ImageType type);
virtual ~QsImageHandle();
Q_DISABLE_COPY_MOVE(QsImageHandle);
[[nodiscard]] QString url() const;
[[nodiscard]] virtual QString url() const;
virtual QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
virtual QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);
@ -37,3 +35,14 @@ private:
QQmlImageProviderBase::ImageType type;
QString id;
};
class QsIndexedImageHandle: public QsImageHandle {
public:
explicit QsIndexedImageHandle(QQmlImageProviderBase::ImageType type): QsImageHandle(type) {}
[[nodiscard]] QString url() const override;
void imageChanged();
private:
quint32 changeIndex = 0;
};

View file

@ -29,5 +29,6 @@ headers = [
"qsmenuanchor.hpp",
"clock.hpp",
"scriptmodel.hpp",
"colorquantizer.hpp",
]
-----

View file

@ -42,6 +42,24 @@ QString QuickshellScreenInfo::name() const {
return this->screen->name();
}
QString QuickshellScreenInfo::model() const {
if (this->screen == nullptr) {
this->warnDangling();
return "{ NULL SCREEN }";
}
return this->screen->model();
}
QString QuickshellScreenInfo::serialNumber() const {
if (this->screen == nullptr) {
this->warnDangling();
return "{ NULL SCREEN }";
}
return this->screen->serialNumber();
}
qint32 QuickshellScreenInfo::x() const {
if (this->screen == nullptr) {
this->warnDangling();

View file

@ -29,6 +29,10 @@ class QuickshellScreenInfo: public QObject {
///
/// Usually something like `DP-1`, `HDMI-1`, `eDP-1`.
Q_PROPERTY(QString name READ name CONSTANT);
/// The model of the screen as seen by the operating system.
Q_PROPERTY(QString model READ model CONSTANT);
/// The serial number of the screen as seen by the operating system.
Q_PROPERTY(QString serialNumber READ serialNumber CONSTANT);
Q_PROPERTY(qint32 x READ x NOTIFY geometryChanged);
Q_PROPERTY(qint32 y READ y NOTIFY geometryChanged);
Q_PROPERTY(qint32 width READ width NOTIFY geometryChanged);
@ -49,6 +53,8 @@ public:
bool operator==(QuickshellScreenInfo& other) const;
[[nodiscard]] QString name() const;
[[nodiscard]] QString model() const;
[[nodiscard]] QString serialNumber() const;
[[nodiscard]] qint32 x() const;
[[nodiscard]] qint32 y() const;
[[nodiscard]] qint32 width() const;

View file

@ -59,8 +59,8 @@ QString DBusMenuItem::icon() const {
this->iconName,
this->menu->iconThemePath.value().join(':')
);
} else if (this->image != nullptr) {
return this->image->url();
} else if (this->image.hasData()) {
return this->image.url();
} else return nullptr;
}
@ -113,7 +113,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
auto originalEnabled = this->mEnabled;
auto originalVisible = this->visible;
auto originalIconName = this->iconName;
auto* originalImage = this->image;
auto imageChanged = false;
auto originalIsSeparator = this->mSeparator;
auto originalButtonType = this->mButtonType;
auto originalToggleState = this->mCheckState;
@ -173,12 +173,16 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
if (iconData.canConvert<QByteArray>()) {
auto data = iconData.value<QByteArray>();
if (data.isEmpty()) {
this->image = nullptr;
} else if (this->image == nullptr || this->image->data != data) {
this->image = new DBusMenuPngImage(data, this);
imageChanged = this->image.hasData();
this->image.data.clear();
} else if (!this->image.hasData() || this->image.data != data) {
imageChanged = true;
this->image.data = data;
this->image.imageChanged();
}
} else if (removed.isEmpty() || removed.contains("icon-data")) {
this->image = nullptr;
imageChanged = this->image.hasData();
image.data.clear();
}
auto type = properties.value("type");
@ -239,17 +243,13 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
if (this->mSeparator != originalIsSeparator) emit this->isSeparatorChanged();
if (this->displayChildren != originalDisplayChildren) emit this->hasChildrenChanged();
if (this->iconName != originalIconName || this->image != originalImage) {
if (this->image != originalImage) {
delete originalImage;
}
if (this->iconName != originalIconName || imageChanged) {
emit this->iconChanged();
}
qCDebug(logDbusMenu).nospace() << "Updated properties of " << this << " { label=" << this->mText
<< ", enabled=" << this->mEnabled << ", visible=" << this->visible
<< ", iconName=" << this->iconName << ", iconData=" << this->image
<< ", iconName=" << this->iconName << ", iconData=" << &this->image
<< ", separator=" << this->mSeparator
<< ", toggleType=" << this->mButtonType
<< ", toggleState=" << this->mCheckState

View file

@ -30,7 +30,17 @@ namespace qs::dbus::dbusmenu {
using menu::QsMenuEntry;
class DBusMenu;
class DBusMenuPngImage;
class DBusMenuItem;
class DBusMenuPngImage: public QsIndexedImageHandle {
public:
explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {}
[[nodiscard]] bool hasData() const { return !data.isEmpty(); }
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
QByteArray data;
};
///! Menu item shared by an external program.
/// Menu item shared by an external program via the
@ -93,7 +103,7 @@ private:
bool visible = true;
bool mSeparator = false;
QString iconName;
DBusMenuPngImage* image = nullptr;
DBusMenuPngImage image;
menu::QsMenuButtonType::Enum mButtonType = menu::QsMenuButtonType::None;
Qt::CheckState mCheckState = Qt::Unchecked;
bool displayChildren = false;
@ -156,17 +166,6 @@ private:
QDebug operator<<(QDebug debug, DBusMenu* menu);
class DBusMenuPngImage: public QsImageHandle {
public:
explicit DBusMenuPngImage(QByteArray data, DBusMenuItem* parent)
: QsImageHandle(QQuickImageProvider::Image, parent)
, data(std::move(data)) {}
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
QByteArray data;
};
class DBusMenuHandle;
QDebug operator<<(QDebug debug, const DBusMenuHandle* handle);

View file

@ -1,9 +1,12 @@
#include "ipc.hpp"
#include <cstring>
#include <utility>
#include <qcolor.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtypes.h>
#include <qvariant.h>
namespace qs::io::ipc {
@ -14,6 +17,12 @@ const BoolIpcType BoolIpcType::INSTANCE {};
const DoubleIpcType DoubleIpcType::INSTANCE {};
const ColorIpcType ColorIpcType::INSTANCE {};
void* IpcType::copyStorage(const void* data) const {
auto* storage = this->createStorage();
memcpy(storage, data, this->size());
return storage;
}
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
@ -70,12 +79,18 @@ void IpcTypeSlot::replace(void* value) {
this->storage = value;
}
void IpcTypeSlot::replace(const QVariant& value) {
this->replace(this->mType->copyStorage(value.constData()));
}
const char* VoidIpcType::name() const { return "void"; }
const char* VoidIpcType::genericArgumentName() const { return "void"; }
qsizetype VoidIpcType::size() const { return 0; }
// string
const char* StringIpcType::name() const { return "string"; }
const char* StringIpcType::genericArgumentName() const { return "QString"; }
qsizetype StringIpcType::size() const { return sizeof(QString); }
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
void* StringIpcType::createStorage() const { return new QString(); }
@ -84,6 +99,7 @@ void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QStrin
// int
const char* IntIpcType::name() const { return "int"; }
const char* IntIpcType::genericArgumentName() const { return "int"; }
qsizetype IntIpcType::size() const { return sizeof(int); }
void* IntIpcType::fromString(const QString& string) const {
auto ok = false;
@ -100,6 +116,7 @@ void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slo
// bool
const char* BoolIpcType::name() const { return "bool"; }
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
qsizetype BoolIpcType::size() const { return sizeof(bool); }
void* BoolIpcType::fromString(const QString& string) const {
if (string == "true") return new bool(true);
@ -121,6 +138,7 @@ void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(s
// double
const char* DoubleIpcType::name() const { return "real"; }
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
qsizetype DoubleIpcType::size() const { return sizeof(double); }
void* DoubleIpcType::fromString(const QString& string) const {
auto ok = false;
@ -139,6 +157,7 @@ void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double
// color
const char* ColorIpcType::name() const { return "color"; }
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
qsizetype ColorIpcType::size() const { return sizeof(QColor); }
void* ColorIpcType::fromString(const QString& string) const {
auto color = QColor::fromString(string);
@ -167,6 +186,10 @@ QString WireFunctionDefinition::toString() const {
return "function " % this->name % '(' % paramString % "): " % this->returnType;
}
QString WirePropertyDefinition::toString() const {
return "property " % this->name % ": " % this->type;
}
QString WireTargetDefinition::toString() const {
QString accum = "target " % this->name;
@ -174,6 +197,10 @@ QString WireTargetDefinition::toString() const {
accum += "\n " % func.toString();
}
for (const auto& prop: this->properties) {
accum += "\n " % prop.toString();
}
return accum;
}

View file

@ -3,6 +3,7 @@
#include <qcontainerfwd.h>
#include <qobjectdefs.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include "../ipc/ipc.hpp"
@ -21,10 +22,12 @@ public:
[[nodiscard]] virtual const char* name() const = 0;
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
[[nodiscard]] virtual qsizetype size() const = 0;
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
virtual void destroyStorage(void* /*slot*/) const {}
void* copyStorage(const void* data) const;
static const IpcType* ipcType(const QMetaType& metaType);
};
@ -43,6 +46,7 @@ public:
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
void replace(void* value);
void replace(const QVariant& value);
private:
const IpcType* mType = nullptr;
@ -53,6 +57,7 @@ class VoidIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
static const VoidIpcType INSTANCE;
};
@ -61,6 +66,7 @@ class StringIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
@ -73,6 +79,7 @@ class IntIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
@ -85,6 +92,7 @@ class BoolIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
@ -97,6 +105,7 @@ class DoubleIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
@ -109,6 +118,7 @@ class ColorIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
@ -127,13 +137,23 @@ struct WireFunctionDefinition {
DEFINE_SIMPLE_DATASTREAM_OPS(WireFunctionDefinition, data.name, data.returnType, data.arguments);
struct WireTargetDefinition {
struct WirePropertyDefinition {
QString name;
QVector<WireFunctionDefinition> functions;
QString type;
[[nodiscard]] QString toString() const;
};
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions);
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
struct WireTargetDefinition {
QString name;
QVector<WireFunctionDefinition> functions;
QVector<WirePropertyDefinition> properties;
[[nodiscard]] QString toString() const;
};
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties);
} // namespace qs::io::ipc

View file

@ -21,16 +21,17 @@ namespace qs::io::ipc::comm {
struct NoCurrentGeneration: std::monostate {};
struct TargetNotFound: std::monostate {};
struct FunctionNotFound: std::monostate {};
struct EntryNotFound: std::monostate {};
using QueryResponse = std::variant<
std::monostate,
NoCurrentGeneration,
TargetNotFound,
FunctionNotFound,
EntryNotFound,
QVector<WireTargetDefinition>,
WireTargetDefinition,
WireFunctionDefinition>;
WireFunctionDefinition,
WirePropertyDefinition>;
void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto resp = conn->responseStream<QueryResponse>();
@ -44,16 +45,24 @@ void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto* handler = registry->findHandler(this->target);
if (handler) {
if (this->function.isEmpty()) {
if (this->name.isEmpty()) {
resp << handler->wireDef();
} else {
auto* func = handler->findFunction(this->function);
auto* func = handler->findFunction(this->name);
if (func) {
resp << func->wireDef();
} else {
resp << FunctionNotFound();
return;
}
auto* prop = handler->findProperty(this->name);
if (prop) {
resp << prop->wireDef();
return;
}
resp << EntryNotFound();
}
} else {
resp << TargetNotFound();
@ -64,8 +73,8 @@ void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
}
}
int queryMetadata(IpcClient* client, const QString& target, const QString& function) {
client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .function = function}));
int queryMetadata(IpcClient* client, const QString& target, const QString& name) {
client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .name = name}));
QueryResponse slot;
if (!client->waitForResponse(slot)) return -1;
@ -82,9 +91,11 @@ int queryMetadata(IpcClient* client, const QString& target, const QString& funct
qCInfo(logBare).noquote() << std::get<WireTargetDefinition>(slot).toString();
} else if (std::holds_alternative<WireFunctionDefinition>(slot)) {
qCInfo(logBare).noquote() << std::get<WireFunctionDefinition>(slot).toString();
} else if (std::holds_alternative<WirePropertyDefinition>(slot)) {
qCInfo(logBare).noquote() << std::get<WirePropertyDefinition>(slot).toString();
} else if (std::holds_alternative<TargetNotFound>(slot)) {
qCCritical(logBare) << "Target not found.";
} else if (std::holds_alternative<FunctionNotFound>(slot)) {
} else if (std::holds_alternative<EntryNotFound>(slot)) {
qCCritical(logBare) << "Function not found.";
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
qCCritical(logBare) << "Not ready to accept queries yet.";
@ -119,7 +130,7 @@ using StringCallResponse = std::variant<
std::monostate,
NoCurrentGeneration,
TargetNotFound,
FunctionNotFound,
EntryNotFound,
ArgParseFailed,
Completed>;
@ -137,7 +148,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto* func = handler->findFunction(this->function);
if (!func) {
resp << FunctionNotFound();
resp << EntryNotFound();
return;
}
@ -223,7 +234,7 @@ int callFunction(
qCCritical(logBare).noquote() << "Function definition:" << error.definition.toString();
} else if (std::holds_alternative<TargetNotFound>(slot)) {
qCCritical(logBare) << "Target not found.";
} else if (std::holds_alternative<FunctionNotFound>(slot)) {
} else if (std::holds_alternative<EntryNotFound>(slot)) {
qCCritical(logBare) << "Function not found.";
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
qCCritical(logBare) << "Not ready to accept queries yet.";
@ -233,4 +244,74 @@ int callFunction(
return -1;
}
struct PropertyValue {
QString value;
};
DEFINE_SIMPLE_DATASTREAM_OPS(PropertyValue, data.value);
using StringPropReadResponse =
std::variant<std::monostate, NoCurrentGeneration, TargetNotFound, EntryNotFound, PropertyValue>;
void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto resp = conn->responseStream<StringPropReadResponse>();
if (auto* generation = EngineGeneration::currentGeneration()) {
auto* registry = IpcHandlerRegistry::forGeneration(generation);
auto* handler = registry->findHandler(this->target);
if (!handler) {
resp << TargetNotFound();
return;
}
auto* prop = handler->findProperty(this->property);
if (!prop) {
resp << EntryNotFound();
return;
}
auto slot = IpcTypeSlot(prop->type);
prop->read(handler, slot);
resp << PropertyValue {
.value = slot.type()->toString(slot.get()),
};
} else {
conn->respond(StringCallResponse(NoCurrentGeneration()));
}
}
int getProperty(IpcClient* client, const QString& target, const QString& property) {
if (target.isEmpty()) {
qCCritical(logBare) << "Target required to send message.";
return -1;
} else if (property.isEmpty()) {
qCCritical(logBare) << "Property required to send message.";
return -1;
}
client->sendMessage(IpcCommand(StringPropReadCommand {.target = target, .property = property}));
StringPropReadResponse slot;
if (!client->waitForResponse(slot)) return -1;
if (std::holds_alternative<PropertyValue>(slot)) {
auto& result = std::get<PropertyValue>(slot);
QTextStream(stdout) << result.value << Qt::endl;
return 0;
} else if (std::holds_alternative<TargetNotFound>(slot)) {
qCCritical(logBare) << "Target not found.";
} else if (std::holds_alternative<EntryNotFound>(slot)) {
qCCritical(logBare) << "Property not found.";
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
qCCritical(logBare) << "Not ready to accept queries yet.";
} else {
qCCritical(logIpc) << "Received invalid IPC response from" << client;
}
return -1;
}
} // namespace qs::io::ipc::comm

View file

@ -2,6 +2,7 @@
#include <qcontainerfwd.h>
#include <qflags.h>
#include <qtypes.h>
#include "../ipc/ipc.hpp"
@ -9,12 +10,12 @@ namespace qs::io::ipc::comm {
struct QueryMetadataCommand {
QString target;
QString function;
QString name;
void exec(qs::ipc::IpcServerConnection* conn) const;
};
DEFINE_SIMPLE_DATASTREAM_OPS(QueryMetadataCommand, data.target, data.function);
DEFINE_SIMPLE_DATASTREAM_OPS(QueryMetadataCommand, data.target, data.name);
struct StringCallCommand {
QString target;
@ -27,7 +28,7 @@ struct StringCallCommand {
DEFINE_SIMPLE_DATASTREAM_OPS(StringCallCommand, data.target, data.function, data.arguments);
void handleMsg(qs::ipc::IpcServerConnection* conn);
int queryMetadata(qs::ipc::IpcClient* client, const QString& target, const QString& function);
int queryMetadata(qs::ipc::IpcClient* client, const QString& target, const QString& name);
int callFunction(
qs::ipc::IpcClient* client,
@ -36,4 +37,15 @@ int callFunction(
const QVector<QString>& arguments
);
struct StringPropReadCommand {
QString target;
QString property;
void exec(qs::ipc::IpcServerConnection* conn) const;
};
DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property);
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
} // namespace qs::io::ipc::comm

View file

@ -107,6 +107,32 @@ WireFunctionDefinition IpcFunction::wireDef() const {
return wire;
}
bool IpcProperty::resolve(QString& error) {
this->type = IpcType::ipcType(this->property.metaType());
if (!this->type) {
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
return false;
}
return true;
}
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
slot.replace(this->property.read(target));
}
QString IpcProperty::toString() const {
return QString("property ") % this->property.name() % ": " % this->type->name();
}
WirePropertyDefinition IpcProperty::wireDef() const {
WirePropertyDefinition wire;
wire.name = this->property.name();
wire.type = this->type->name();
return wire;
}
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
for (const auto& arg: function.argumentTypes) {
this->argumentSlots.emplace_back(arg);
@ -153,6 +179,21 @@ void IpcHandler::onPostReload() {
}
}
for (auto i = smeta.propertyCount(); i != meta->propertyCount(); i++) {
const auto& property = meta->property(i);
if (!property.isReadable() || !property.hasNotifySignal()) continue;
auto ipcProp = IpcProperty(property);
QString error;
if (!ipcProp.resolve(error)) {
qmlWarning(this).nospace().noquote()
<< "Error parsing property \"" << property.name() << "\": " << error;
} else {
this->propertyMap.insert(property.name(), ipcProp);
}
}
this->complete = true;
this->updateRegistration();
@ -270,6 +311,10 @@ WireTargetDefinition IpcHandler::wireDef() const {
wire.functions += func.wireDef();
}
for (const auto& prop: this->propertyMap.values()) {
wire.properties += prop.wireDef();
}
return wire;
}
@ -307,6 +352,13 @@ IpcFunction* IpcHandler::findFunction(const QString& name) {
else return &*itr;
}
IpcProperty* IpcHandler::findProperty(const QString& name) {
auto itr = this->propertyMap.find(name);
if (itr == this->propertyMap.end()) return nullptr;
else return &*itr;
}
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
return this->handlers.value(target);
}

View file

@ -53,14 +53,28 @@ private:
friend class IpcFunction;
};
class IpcProperty {
public:
explicit IpcProperty(QMetaProperty property): property(property) {}
bool resolve(QString& error);
void read(QObject* target, IpcTypeSlot& slot) const;
[[nodiscard]] QString toString() const;
[[nodiscard]] WirePropertyDefinition wireDef() const;
QMetaProperty property;
const IpcType* type = nullptr;
};
class IpcHandlerRegistry;
///! Handler for IPC message calls.
/// Each IpcHandler is registered into a per-instance map by its unique @@target.
/// Functions defined on the IpcHandler can be called by `qs msg`.
/// Functions and properties defined on the IpcHandler can be accessed via `qs ipc`.
///
/// #### Handler Functions
/// IPC handler functions can be called by `qs msg` as long as they have at most 10
/// IPC handler functions can be called by `qs ipc call` as long as they have at most 10
/// arguments, and all argument types along with the return type are listed below.
///
/// **Argument and return types must be explicitly specified or they will not
@ -112,9 +126,9 @@ class IpcHandlerRegistry;
/// }
/// }
/// ```
/// The list of registered targets can be inspected using `qs msg -s`.
/// The list of registered targets can be inspected using `qs ipc show`.
/// ```sh
/// $ qs msg -s
/// $ qs ipc show
/// target rect
/// function setColor(color: color): void
/// function getColor(): color
@ -124,18 +138,22 @@ class IpcHandlerRegistry;
/// function getRadius(): int
/// ```
///
/// and then invoked using `qs msg`.
/// and then invoked using `qs ipc call`.
/// ```sh
/// $ qs msg rect setColor orange
/// $ qs msg rect setAngle 40.5
/// $ qs msg rect setRadius 30
/// $ qs msg rect getColor
/// $ qs ipc call rect setColor orange
/// $ qs ipc call rect setAngle 40.5
/// $ qs ipc call rect setRadius 30
/// $ qs ipc call rect getColor
/// #ffffa500
/// $ qs msg rect getAngle
/// $ qs ipc call rect getAngle
/// 40.5
/// $ qs msg rect getRadius
/// $ qs ipc call rect getRadius
/// 30
/// ```
///
/// #### Properties
/// Properties of an IpcHanlder can be read using `qs ipc prop get` as long as they are
/// of an IPC compatible type. See the table above for compatible types.
class IpcHandler
: public QObject
, public PostReloadHook {
@ -162,12 +180,16 @@ public:
QString listMembers(qsizetype indent);
[[nodiscard]] IpcFunction* findFunction(const QString& name);
[[nodiscard]] IpcProperty* findProperty(const QString& name);
[[nodiscard]] WireTargetDefinition wireDef() const;
signals:
void enabledChanged();
void targetChanged();
private slots:
//void handleIpcPropertyChange();
private:
void updateRegistration(bool destroying = false);
@ -183,6 +205,7 @@ private:
bool complete = false;
QHash<QString, IpcFunction> functionMap;
QHash<QString, IpcProperty> propertyMap;
friend class IpcHandlerRegistry;
};

View file

@ -15,6 +15,7 @@ using IpcCommand = std::variant<
std::monostate,
IpcKillCommand,
qs::io::ipc::comm::QueryMetadataCommand,
qs::io::ipc::comm::StringCallCommand>;
qs::io::ipc::comm::StringCallCommand,
qs::io::ipc::comm::StringPropReadCommand>;
} // namespace qs::ipc

View file

@ -102,9 +102,10 @@ int locateConfigFile(CommandState& cmd, QString& path) {
return 0;
}
void sortInstances(QVector<InstanceLockInfo>& list) {
std::ranges::sort(list, [](const InstanceLockInfo& a, const InstanceLockInfo& b) {
return a.instance.launchTime < b.instance.launchTime;
void sortInstances(QVector<InstanceLockInfo>& list, bool newestFirst) {
std::ranges::sort(list, [=](const InstanceLockInfo& a, const InstanceLockInfo& b) {
auto r = a.instance.launchTime < b.instance.launchTime;
return newestFirst ? !r : r;
});
};
@ -153,7 +154,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance) {
path = QDir(basePath->filePath("by-path")).filePath(pathId);
auto instances = QsPaths::collectInstances(path);
sortInstances(instances);
sortInstances(instances, cmd.config.newest);
if (instances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath;
@ -227,7 +228,7 @@ int listInstances(CommandState& cmd) {
qCInfo(logBare) << "Use --all to list all instances.";
}
} else {
sortInstances(instances);
sortInstances(instances, cmd.config.newest);
if (cmd.output.json) {
auto array = QJsonArray();
@ -284,26 +285,23 @@ int killInstances(CommandState& cmd) {
});
}
int msgInstance(CommandState& cmd) {
int ipcCommand(CommandState& cmd) {
InstanceLockInfo instance;
auto r = selectInstance(cmd, &instance);
if (r != 0) return r;
return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) {
if (cmd.ipc.info) {
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.function);
if (*cmd.ipc.show || cmd.ipc.showOld) {
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
} else if (*cmd.ipc.getprop) {
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
} else {
QVector<QString> arguments;
for (auto& arg: cmd.ipc.arguments) {
arguments += *arg;
}
return qs::io::ipc::comm::callFunction(
&client,
*cmd.ipc.target,
*cmd.ipc.function,
arguments
);
return qs::io::ipc::comm::callFunction(&client, *cmd.ipc.target, *cmd.ipc.name, arguments);
}
return -1;
@ -422,8 +420,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
return listInstances(state);
} else if (*state.subcommand.kill) {
return killInstances(state);
} else if (*state.subcommand.msg) {
return msgInstance(state);
} else if (*state.subcommand.msg || *state.ipc.ipc) {
return ipcCommand(state);
} else {
if (strcmp(qVersion(), QT_VERSION_STR) != 0) {
qWarning() << "\033[31mQuickshell was built against Qt" << QT_VERSION_STR

View file

@ -49,6 +49,7 @@ struct CommandState {
QStringOption path;
QStringOption manifest;
QStringOption name;
bool newest = false;
} config;
struct {
@ -67,9 +68,13 @@ struct CommandState {
} output;
struct {
bool info = false;
CLI::App* ipc = nullptr;
CLI::App* show = nullptr;
CLI::App* call = nullptr;
CLI::App* getprop = nullptr;
bool showOld = false;
QStringOption target;
QStringOption function;
QStringOption name;
std::vector<QStringOption> arguments;
} ipc;

View file

@ -16,7 +16,7 @@ int parseCommand(int argc, char** argv, CommandState& state) {
.argv = argv,
};
auto addConfigSelection = [&](CLI::App* cmd) {
auto addConfigSelection = [&](CLI::App* cmd, bool withNewestOption = false) {
auto* group = cmd->add_option_group("Config Selection")
->description("If no options in this group are specified,\n"
"$XDG_CONFIG_HOME/quickshell/shell.qml will be used.");
@ -37,6 +37,11 @@ int parseCommand(int argc, char** argv, CommandState& state) {
"otherwise it is the name of a folder in $XDG_CONFIG_HOME/quickshell.")
->envname("QS_CONFIG_NAME");
if (withNewestOption) {
group->add_flag("-n,--newest", state.config.newest)
->description("Operate on the most recently launched instance instead of the oldest");
}
return group;
};
@ -130,7 +135,7 @@ int parseCommand(int argc, char** argv, CommandState& state) {
->description("Rules to apply to the log being read, in the format of QT_LOGGING_RULES.");
auto* instance = addInstanceSelection(sub)->excludes(file);
addConfigSelection(sub)->excludes(instance)->excludes(file);
addConfigSelection(sub, true)->excludes(instance)->excludes(file);
addLoggingOptions(sub, false);
state.subcommand.log = sub;
@ -146,7 +151,7 @@ int parseCommand(int argc, char** argv, CommandState& state) {
sub->add_flag("-j,--json", state.output.json, "Output the list as a json.");
addConfigSelection(sub)->excludes(all);
addConfigSelection(sub, true)->excludes(all);
addLoggingOptions(sub, false, true);
state.subcommand.list = sub;
@ -156,36 +161,71 @@ int parseCommand(int argc, char** argv, CommandState& state) {
auto* sub = cli->add_subcommand("kill", "Kill quickshell instances.");
//sub->add_flag("-a,--all", "Kill all matching instances instead of just one.");
auto* instance = addInstanceSelection(sub);
addConfigSelection(sub)->excludes(instance);
addConfigSelection(sub, true)->excludes(instance);
addLoggingOptions(sub, false, true);
state.subcommand.kill = sub;
}
{
auto* sub = cli->add_subcommand("msg", "Send messages to IpcHandlers.")->require_option();
auto* target = sub->add_option("target", state.ipc.target, "The target to message.");
auto* function = sub->add_option("function", state.ipc.function)
->description("The function to call in the target.")
->needs(target);
auto* arguments = sub->add_option("arguments", state.ipc.arguments)
->description("Arguments to the called function.")
->needs(function)
->allow_extra_args();
sub->add_flag("-s,--show", state.ipc.info)
->description("Print information about a function or target if given, or all available "
"targets if not.")
->excludes(arguments);
auto* sub = cli->add_subcommand("ipc", "Communicate with other Quickshell instances.")
->require_subcommand();
state.ipc.ipc = sub;
auto* instance = addInstanceSelection(sub);
addConfigSelection(sub)->excludes(instance);
addConfigSelection(sub, true)->excludes(instance);
addLoggingOptions(sub, false, true);
sub->require_option();
{
auto* show = sub->add_subcommand("show", "Print information about available IPC targets.");
state.ipc.show = show;
}
{
auto* call = sub->add_subcommand("call", "Call an IpcHandler function.");
state.ipc.call = call;
call->add_option("target", state.ipc.target, "The target to message.");
call->add_option("function", state.ipc.name)
->description("The function to call in the target.");
call->add_option("arguments", state.ipc.arguments)
->description("Arguments to the called function.")
->allow_extra_args();
}
{
auto* prop =
sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand();
{
auto* get = prop->add_subcommand("get", "Read the value of a property.");
state.ipc.getprop = get;
get->add_option("target", state.ipc.target, "The target to read the property of.");
get->add_option("property", state.ipc.name)->description("The property to read.");
}
}
}
{
auto* sub = cli->add_subcommand("msg", "[DEPRECATED] Moved to `ipc call`.")->require_option();
sub->add_option("target", state.ipc.target, "The target to message.");
sub->add_option("function", state.ipc.name)->description("The function to call in the target.");
sub->add_option("arguments", state.ipc.arguments)
->description("Arguments to the called function.")
->allow_extra_args();
sub->add_flag("-s,--show", state.ipc.showOld)
->description("Print information about a function or target if given, or all available "
"targets if not.");
auto* instance = addInstanceSelection(sub);
addConfigSelection(sub, true)->excludes(instance);
addLoggingOptions(sub, false, true);
state.subcommand.msg = sub;
}

View file

@ -1,7 +1,5 @@
#pragma once
#include <utility>
#include <qdbusargument.h>
#include <qimage.h>
#include <qobject.h>
@ -23,14 +21,22 @@ struct DBusNotificationImage {
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationImage& pixmap);
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationImage& pixmap);
class NotificationImage: public QsImageHandle {
class NotificationImage: public QsIndexedImageHandle {
public:
explicit NotificationImage(DBusNotificationImage image, QObject* parent)
: QsImageHandle(QQuickAsyncImageProvider::Image, parent)
, image(std::move(image)) {}
explicit NotificationImage(): QsIndexedImageHandle(QQuickAsyncImageProvider::Image) {}
[[nodiscard]] bool hasData() const { return !this->image.data.isEmpty(); }
void clear() { this->image.data.clear(); }
[[nodiscard]] DBusNotificationImage& writeImage() {
this->imageChanged();
return this->image;
}
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
private:
DBusNotificationImage image;
};
} // namespace qs::service::notifications

View file

@ -1,5 +1,4 @@
#include "notification.hpp"
#include <utility>
#include <qcontainerfwd.h>
#include <qdbusargument.h>
@ -117,13 +116,12 @@ void Notification::updateProperties(
QString imagePath;
if (!imageDataName.isEmpty()) {
if (imageDataName.isEmpty()) {
this->mImagePixmap.clear();
} else {
auto value = hints.value(imageDataName).value<QDBusArgument>();
DBusNotificationImage image;
value >> image;
if (this->mImagePixmap) this->mImagePixmap->deleteLater();
this->mImagePixmap = new NotificationImage(std::move(image), this);
imagePath = this->mImagePixmap->url();
value >> this->mImagePixmap.writeImage();
imagePath = this->mImagePixmap.url();
}
// don't store giant byte arrays longer than necessary
@ -131,7 +129,7 @@ void Notification::updateProperties(
hints.remove("image_data");
hints.remove("icon_data");
if (!this->mImagePixmap) {
if (!this->mImagePixmap.hasData()) {
QString imagePathName;
if (hints.contains("image-path")) imagePathName = "image-path";
else if (hints.contains("image_path")) imagePathName = "image_path";

View file

@ -12,11 +12,10 @@
#include "../../core/retainable.hpp"
#include "../../core/util.hpp"
#include "dbusimage.hpp"
namespace qs::service::notifications {
class NotificationImage;
///! The urgency level of a Notification.
/// See @@Notification.urgency.
class NotificationUrgency: public QObject {
@ -187,7 +186,7 @@ private:
quint32 mId;
NotificationCloseReason::Enum mCloseReason = NotificationCloseReason::Dismissed;
bool mLastGeneration = false;
NotificationImage* mImagePixmap = nullptr;
NotificationImage mImagePixmap;
QList<NotificationAction*> mActions;
// clang-format off

View file

@ -282,7 +282,7 @@ void StatusNotifierItem::onGetAllFailed() const {
}
TrayImageHandle::TrayImageHandle(StatusNotifierItem* item)
: QsImageHandle(QQmlImageProviderBase::Pixmap, item)
: QsImageHandle(QQmlImageProviderBase::Pixmap)
, item(item) {}
QPixmap

View file

@ -34,7 +34,7 @@
This protocol exposes hyprland-specific wl_surface properties.
</description>
<interface name="hyprland_surface_manager_v1" version="1">
<interface name="hyprland_surface_manager_v1" version="2">
<description summary="manager for hyprland surface objects">
This interface allows a client to create hyprland surface objects.
</description>
@ -63,7 +63,7 @@
</enum>
</interface>
<interface name="hyprland_surface_v1" version="1">
<interface name="hyprland_surface_v1" version="2">
<description summary="hyprland-specific wl_surface properties">
This interface allows access to hyprland-specific properties of a wl_surface.
@ -96,5 +96,31 @@
<entry name="no_surface" value="0" summary="wl_surface was destroyed"/>
<entry name="out_of_range" value="1" summary="given opacity was not in the range 0.0 - 1.0 (inclusive)"/>
</enum>
<request name="set_visible_region" since="2">
<description summary="set the visible region of the surface">
This request sets the region of the surface that contains visible content.
Visible content refers to content that has an alpha value greater than zero.
The visible region is an optimization hint for the compositor that lets it
avoid drawing parts of the surface that are not visible. Setting a visible region
that does not contain all content in the surface may result in missing content
not being drawn.
The visible region is specified in buffer-local coordinates.
The compositor ignores the parts of the visible region that fall outside of the surface.
When all parts of the region fall outside of the buffer geometry, the compositor may
avoid rendering the surface entirely.
The initial value for the visible region is empty. Setting the
visible region has copy semantics, and the wl_region object can be destroyed immediately.
A NULL wl_region causes the visible region to be set to empty.
Does not take effect until wl_surface.commit is called.
</description>
<arg name="region" type="object" interface="wl_region" allow-null="true"/>
</request>
</interface>
</protocol>

View file

@ -7,13 +7,13 @@
namespace qs::hyprland::surface::impl {
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(1) {
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(2) {
this->initialize();
}
HyprlandSurface*
HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) {
return new HyprlandSurface(this->get_hyprland_surface(surface->surface()));
return new HyprlandSurface(this->get_hyprland_surface(surface->surface()), surface);
}
HyprlandSurfaceManager* HyprlandSurfaceManager::instance() {

View file

@ -1,17 +1,20 @@
#include "qml.hpp"
#include <memory>
#include <private/qhighdpiscaling_p.h>
#include <private/qwaylandwindow_p.h>
#include <qlogging.h>
#include <qobject.h>
#include <qqmlinfo.h>
#include <qregion.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include <qwindow.h>
#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp"
#include "../../../window/windowinterface.hpp"
#include "../../util.hpp"
#include "manager.hpp"
#include "surface.hpp"
@ -40,6 +43,15 @@ HyprlandWindow::HyprlandWindow(ProxyWindowBase* window): QObject(nullptr), proxy
&HyprlandWindow::onWindowConnected
);
QObject::connect(window, &ProxyWindowBase::polished, this, &HyprlandWindow::onWindowPolished);
QObject::connect(
window,
&ProxyWindowBase::devicePixelRatioChanged,
this,
&HyprlandWindow::updateVisibleMask
);
QObject::connect(window, &QObject::destroyed, this, &HyprlandWindow::onProxyWindowDestroyed);
if (window->backingWindow()) {
@ -60,14 +72,76 @@ void HyprlandWindow::setOpacity(qreal opacity) {
this->mOpacity = opacity;
if (this->surface) {
this->surface->setOpacity(opacity);
qs::wayland::util::scheduleCommit(this->proxyWindow);
if (this->surface && this->proxyWindow) {
this->pendingPolish.opacity = true;
this->proxyWindow->schedulePolish();
}
emit this->opacityChanged();
}
PendingRegion* HyprlandWindow::visibleMask() const { return this->mVisibleMask; }
void HyprlandWindow::setVisibleMask(PendingRegion* mask) {
if (mask == this->mVisibleMask) return;
if (this->mVisibleMask) {
QObject::disconnect(this->mVisibleMask, nullptr, this, nullptr);
}
this->mVisibleMask = mask;
if (mask) {
QObject::connect(mask, &QObject::destroyed, this, &HyprlandWindow::onVisibleMaskDestroyed);
QObject::connect(mask, &PendingRegion::changed, this, &HyprlandWindow::updateVisibleMask);
}
this->updateVisibleMask();
emit this->visibleMaskChanged();
}
void HyprlandWindow::onVisibleMaskDestroyed() {
this->mVisibleMask = nullptr;
this->updateVisibleMask();
emit this->visibleMaskChanged();
}
void HyprlandWindow::updateVisibleMask() {
if (!this->surface || !this->proxyWindow) return;
this->pendingPolish.visibleMask = true;
this->proxyWindow->schedulePolish();
}
void HyprlandWindow::onWindowPolished() {
if (!this->surface) return;
if (this->pendingPolish.opacity) {
this->surface->setOpacity(this->mOpacity);
this->pendingPolish.opacity = false;
}
if (this->pendingPolish.visibleMask) {
QRegion mask;
if (this->mVisibleMask != nullptr) {
mask =
this->mVisibleMask->applyTo(QRect(0, 0, this->mWindow->width(), this->mWindow->height()));
}
auto dpr = this->proxyWindow->devicePixelRatio();
if (dpr != 1.0) {
mask = QHighDpi::scale(mask, dpr);
}
if (mask.isEmpty() && this->mVisibleMask) {
mask = QRect(-1, -1, 1, 1);
}
this->surface->setVisibleRegion(mask);
this->pendingPolish.visibleMask = false;
}
}
void HyprlandWindow::onWindowConnected() {
this->mWindow = this->proxyWindow->backingWindow();
// disconnected by destructor
@ -86,33 +160,46 @@ void HyprlandWindow::onWindowVisibleChanged() {
if (!this->mWindow->handle()) {
this->mWindow->create();
}
}
this->mWaylandWindow = dynamic_cast<QWaylandWindow*>(this->mWindow->handle());
auto* window = dynamic_cast<QWaylandWindow*>(this->mWindow->handle());
if (window == this->mWaylandWindow) return;
if (this->mWaylandWindow) {
// disconnected by destructor
if (this->mWaylandWindow) {
QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
}
QObject::connect(
this->mWaylandWindow,
&QWaylandWindow::surfaceCreated,
this,
&HyprlandWindow::onWaylandSurfaceCreated
);
this->mWaylandWindow = window;
if (!window) return;
QObject::connect(
this->mWaylandWindow,
&QWaylandWindow::surfaceDestroyed,
this,
&HyprlandWindow::onWaylandSurfaceDestroyed
);
QObject::connect(
this->mWaylandWindow,
&QObject::destroyed,
this,
&HyprlandWindow::onWaylandWindowDestroyed
);
if (this->mWaylandWindow->surface()) {
this->onWaylandSurfaceCreated();
}
}
QObject::connect(
this->mWaylandWindow,
&QWaylandWindow::surfaceCreated,
this,
&HyprlandWindow::onWaylandSurfaceCreated
);
QObject::connect(
this->mWaylandWindow,
&QWaylandWindow::surfaceDestroyed,
this,
&HyprlandWindow::onWaylandSurfaceDestroyed
);
if (this->mWaylandWindow->surface()) {
this->onWaylandSurfaceCreated();
}
}
void HyprlandWindow::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }
void HyprlandWindow::onWaylandSurfaceCreated() {
auto* manager = impl::HyprlandSurfaceManager::instance();
@ -122,12 +209,26 @@ void HyprlandWindow::onWaylandSurfaceCreated() {
return;
}
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
this->surface = std::unique_ptr<impl::HyprlandSurface>(ext);
auto v = this->mWaylandWindow->property("hyprland_window_ext");
if (v.canConvert<HyprlandWindow*>()) {
auto* windowExt = v.value<HyprlandWindow*>();
if (windowExt != this && windowExt->surface) {
this->surface.swap(windowExt->surface);
}
}
if (this->mOpacity != 1.0) {
this->surface->setOpacity(this->mOpacity);
qs::wayland::util::scheduleCommit(this->proxyWindow);
if (!this->surface) {
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
this->surface = std::unique_ptr<impl::HyprlandSurface>(ext);
}
this->mWaylandWindow->setProperty("hyprland_window_ext", QVariant::fromValue(this));
this->pendingPolish.opacity = this->mOpacity != 1.0;
this->pendingPolish.visibleMask = this->mVisibleMask;
if (this->pendingPolish.opacity || this->pendingPolish.visibleMask) {
this->proxyWindow->schedulePolish();
}
}
@ -144,8 +245,9 @@ void HyprlandWindow::onProxyWindowDestroyed() {
// Deleting it when the proxy window is deleted will cause a full opacity frame between the destruction of the
// hyprland_surface_v1 and wl_surface objects.
this->proxyWindow = nullptr;
if (this->surface == nullptr) {
this->proxyWindow = nullptr;
this->deleteLater();
}
}

View file

@ -9,6 +9,7 @@
#include <qtypes.h>
#include <qwindow.h>
#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp"
#include "surface.hpp"
@ -31,11 +32,18 @@ namespace qs::hyprland::surface {
/// [hyprland-surface-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-surface-v1.xml
class HyprlandWindow: public QObject {
Q_OBJECT;
// clang-format off
/// A multiplier for the window's overall opacity, ranging from 1.0 to 0.0. Overall opacity includes the opacity of
/// both the window content *and* visual effects such as blur that apply to it.
///
/// Default: 1.0
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged);
/// A hint to the compositor that only certain regions of the surface should be rendered.
/// This can be used to avoid rendering large empty regions of a window which can increase
/// performance, especially if the window is blurred. The mask should include all pixels
/// of the window that do not have an alpha value of 0.
Q_PROPERTY(PendingRegion* visibleMask READ visibleMask WRITE setVisibleMask NOTIFY visibleMaskChanged);
// clang-format on
QML_ELEMENT;
QML_UNCREATABLE("HyprlandWindow can only be used as an attached object.");
QML_ATTACHED(HyprlandWindow);
@ -48,17 +56,25 @@ public:
[[nodiscard]] qreal opacity() const;
void setOpacity(qreal opacity);
[[nodiscard]] PendingRegion* visibleMask() const;
virtual void setVisibleMask(PendingRegion* mask);
static HyprlandWindow* qmlAttachedProperties(QObject* object);
signals:
void opacityChanged();
void visibleMaskChanged();
private slots:
void onWindowConnected();
void onWindowVisibleChanged();
void onWaylandWindowDestroyed();
void onWaylandSurfaceCreated();
void onWaylandSurfaceDestroyed();
void onProxyWindowDestroyed();
void onVisibleMaskDestroyed();
void onWindowPolished();
void updateVisibleMask();
private:
void disconnectWaylandWindow();
@ -67,7 +83,13 @@ private:
QWindow* mWindow = nullptr;
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
struct {
bool opacity : 1 = false;
bool visibleMask : 1 = false;
} pendingPolish;
qreal mOpacity = 1.0;
PendingRegion* mVisibleMask = nullptr;
std::unique_ptr<impl::HyprlandSurface> surface;
};

View file

@ -1,19 +1,53 @@
#include "surface.hpp"
#include <cmath>
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandintegration_p.h>
#include <qlogging.h>
#include <qregion.h>
#include <qtypes.h>
#include <qwayland-hyprland-surface-v1.h>
#include <wayland-client-protocol.h>
#include <wayland-hyprland-surface-v1-client-protocol.h>
#include <wayland-util.h>
namespace qs::hyprland::surface::impl {
HyprlandSurface::HyprlandSurface(::hyprland_surface_v1* surface)
: QtWayland::hyprland_surface_v1(surface) {}
HyprlandSurface::HyprlandSurface(
::hyprland_surface_v1* surface,
QtWaylandClient::QWaylandWindow* backer
)
: QtWayland::hyprland_surface_v1(surface)
, backer(backer)
, backerSurface(backer->surface()) {}
HyprlandSurface::~HyprlandSurface() { this->destroy(); }
bool HyprlandSurface::surfaceEq(wl_surface* surface) const {
return surface == this->backerSurface;
}
void HyprlandSurface::setOpacity(qreal opacity) {
this->set_opacity(wl_fixed_from_double(opacity));
}
void HyprlandSurface::setVisibleRegion(const QRegion& region) {
if (this->version() < HYPRLAND_SURFACE_V1_SET_VISIBLE_REGION_SINCE_VERSION) {
qWarning() << "Cannot set hyprland surface visible region: compositor does not support "
"hyprland_surface_v1.set_visible_region";
return;
}
if (region.isEmpty()) {
this->set_visible_region(nullptr);
} else {
static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance();
auto* display = waylandIntegration->display();
auto* wlRegion = display->createRegion(region);
this->set_visible_region(wlRegion);
wl_region_destroy(wlRegion); // NOLINT(misc-include-cleaner)
}
}
} // namespace qs::hyprland::surface::impl

View file

@ -1,21 +1,31 @@
#pragma once
#include <private/qwaylandwindow_p.h>
#include <qobject.h>
#include <qregion.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qwayland-hyprland-surface-v1.h>
#include <wayland-client-protocol.h>
#include <wayland-hyprland-surface-v1-client-protocol.h>
namespace qs::hyprland::surface::impl {
class HyprlandSurface: public QtWayland::hyprland_surface_v1 {
public:
explicit HyprlandSurface(::hyprland_surface_v1* surface);
explicit HyprlandSurface(::hyprland_surface_v1* surface, QtWaylandClient::QWaylandWindow* backer);
~HyprlandSurface() override;
Q_DISABLE_COPY_MOVE(HyprlandSurface);
[[nodiscard]] bool surfaceEq(wl_surface* surface) const;
void setOpacity(qreal opacity);
void setVisibleRegion(const QRegion& region);
private:
QtWaylandClient::QWaylandWindow* backer;
wl_surface* backerSurface = nullptr;
};
} // namespace qs::hyprland::surface::impl

View file

@ -90,8 +90,8 @@ void WlrLayershell::setHeight(qint32 height) {
}
void WlrLayershell::setScreen(QuickshellScreenInfo* screen) {
this->ProxyWindowBase::setScreen(screen);
this->ext->setUseWindowScreen(screen != nullptr);
this->ProxyWindowBase::setScreen(screen);
}
// NOLINTBEGIN

View file

@ -3,6 +3,7 @@
#include <private/qquickwindow_p.h>
#include <qcoreevent.h>
#include <qevent.h>
#include <qguiapplication.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmlcontext.h>
@ -200,9 +201,6 @@ void ProxyWindowBase::completeWindow() {
if (this->mScreen != nullptr && this->window->screen() != this->mScreen) {
if (this->window->isVisible()) this->window->setVisible(false);
this->window->setScreen(this->mScreen);
} else if (this->mScreen == nullptr) {
this->mScreen = this->window->screen();
QObject::connect(this->mScreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
}
this->setWidth(this->mWidth);
@ -327,39 +325,39 @@ void ProxyWindowBase::setHeight(qint32 height) {
void ProxyWindowBase::setScreen(QuickshellScreenInfo* screen) {
auto* qscreen = screen == nullptr ? nullptr : screen->screen;
if (qscreen == this->mScreen) return;
auto newMScreen = this->mScreen != qscreen;
if (this->mScreen != nullptr) {
if (this->mScreen && newMScreen) {
QObject::disconnect(this->mScreen, nullptr, this, nullptr);
}
if (this->window == nullptr) {
emit this->screenChanged();
} else {
auto reshow = this->isVisibleDirect();
if (reshow) this->setVisibleDirect(false);
if (this->window != nullptr) this->window->setScreen(qscreen);
if (reshow) this->setVisibleDirect(true);
if (this->qscreen() != qscreen) {
this->mScreen = qscreen;
if (this->window == nullptr) {
emit this->screenChanged();
} else if (qscreen) {
auto reshow = this->isVisibleDirect();
if (reshow) this->setVisibleDirect(false);
if (this->window != nullptr) this->window->setScreen(qscreen);
if (reshow) this->setVisibleDirect(true);
}
}
if (qscreen) this->mScreen = qscreen;
else this->mScreen = this->window->screen();
QObject::connect(this->mScreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
if (qscreen && newMScreen) {
QObject::connect(this->mScreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
}
}
void ProxyWindowBase::onScreenDestroyed() { this->mScreen = nullptr; }
QScreen* ProxyWindowBase::qscreen() const {
if (this->window) return this->window->screen();
if (this->mScreen) return this->mScreen;
return QGuiApplication::primaryScreen();
}
QuickshellScreenInfo* ProxyWindowBase::screen() const {
QScreen* qscreen = nullptr;
if (this->window == nullptr) {
if (this->mScreen != nullptr) qscreen = this->mScreen;
} else {
qscreen = this->window->screen();
}
return QuickshellTracked::instance()->screenInfo(qscreen);
return QuickshellTracked::instance()->screenInfo(this->qscreen());
}
QColor ProxyWindowBase::color() const { return this->mColor; }

View file

@ -100,7 +100,8 @@ public:
[[nodiscard]] qreal devicePixelRatio() const;
[[nodiscard]] virtual QuickshellScreenInfo* screen() const;
[[nodiscard]] QScreen* qscreen() const;
[[nodiscard]] QuickshellScreenInfo* screen() const;
virtual void setScreen(QuickshellScreenInfo* screen);
[[nodiscard]] QColor color() const;