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 common.cpp
iconprovider.cpp iconprovider.cpp
scriptmodel.cpp scriptmodel.cpp
colorquantizer.cpp
) )
qt_add_qml_module(quickshell-core qt_add_qml_module(quickshell-core

View file

@ -6,8 +6,6 @@
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include "util.hpp"
SystemClock::SystemClock(QObject* parent): QObject(parent) { SystemClock::SystemClock(QObject* parent): QObject(parent) {
QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::onTimeout); QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::onTimeout);
this->update(); this->update();
@ -48,19 +46,16 @@ void SystemClock::update() {
void SystemClock::setTime(const QDateTime& targetTime) { void SystemClock::setTime(const QDateTime& targetTime) {
auto currentTime = QDateTime::currentDateTime(); auto currentTime = QDateTime::currentDateTime();
auto offset = currentTime.msecsTo(targetTime); auto offset = currentTime.msecsTo(targetTime);
auto dtime = offset > -500 && offset < 500 ? targetTime : currentTime; this->currentTime = offset > -500 && offset < 500 ? targetTime : currentTime;
auto time = dtime.time();
auto secondPrecision = this->mPrecision >= SystemClock::Seconds; auto time = this->currentTime.time();
auto secondChanged = this->setSeconds(secondPrecision ? time.second() : 0); 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; emit this->dateChanged();
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);
} }
void SystemClock::schedule(const QDateTime& targetTime) { 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 nextTime = offset > 0 && offset < 500 ? targetTime : currentTime;
auto baseTimeT = nextTime.time(); auto baseTimeT = nextTime.time();
nextTime.setTime( nextTime.setTime(QTime(
{hourPrecision ? baseTimeT.hour() : 0, hourPrecision ? baseTimeT.hour() : 0,
minutePrecision ? baseTimeT.minute() : 0, minutePrecision ? baseTimeT.minute() : 0,
secondPrecision ? baseTimeT.second() : 0} secondPrecision ? baseTimeT.second() : 0
); ));
if (secondPrecision) nextTime = nextTime.addSecs(1); if (secondPrecision) nextTime = nextTime.addSecs(1);
else if (minutePrecision) nextTime = nextTime.addSecs(60); 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->timer.start(static_cast<qint32>(delay));
this->targetTime = nextTime; 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 <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include "util.hpp"
///! System clock accessor. ///! 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 { class SystemClock: public QObject {
Q_OBJECT; Q_OBJECT;
/// If the clock should update. Defaults to true. /// 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); Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
/// The precision the clock should measure at. Defaults to `SystemClock.Seconds`. /// The precision the clock should measure at. Defaults to `SystemClock.Seconds`.
Q_PROPERTY(SystemClock::Enum precision READ precision WRITE setPrecision NOTIFY precisionChanged); 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. /// 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`. /// 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`. /// 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; QML_ELEMENT;
public: public:
@ -43,12 +65,15 @@ public:
[[nodiscard]] SystemClock::Enum precision() const; [[nodiscard]] SystemClock::Enum precision() const;
void setPrecision(SystemClock::Enum precision); 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: signals:
void enabledChanged(); void enabledChanged();
void precisionChanged(); void precisionChanged();
void hoursChanged(); void dateChanged();
void minutesChanged();
void secondsChanged();
private slots: private slots:
void onTimeout(); void onTimeout();
@ -56,17 +81,11 @@ private slots:
private: private:
bool mEnabled = true; bool mEnabled = true;
SystemClock::Enum mPrecision = SystemClock::Seconds; SystemClock::Enum mPrecision = SystemClock::Seconds;
quint32 mHours = 0;
quint32 mMinutes = 0;
quint32 mSeconds = 0;
QTimer timer; QTimer timer;
QDateTime currentTime;
QDateTime targetTime; QDateTime targetTime;
void update(); void update();
void setTime(const QDateTime& targetTime); void setTime(const QDateTime& targetTime);
void schedule(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; currentArgument += c;
escape = 0; escape = 0;
} else if (c == u'"') { } else if (c == u'"' || c == u'\'') {
parsingString = false; parsingString = false;
} else { } else {
currentArgument += c; currentArgument += c;
@ -229,7 +229,7 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
percent = false; percent = false;
} else if (c == '%') { } else if (c == '%') {
percent = true; percent = true;
} else if (c == u'"') { } else if (c == u'"' || c == u'\'') {
parsingString = true; parsingString = true;
} else if (c == u' ') { } else if (c == u' ') {
if (!currentArgument.isEmpty()) { if (!currentArgument.isEmpty()) {

View file

@ -1,5 +1,6 @@
#include "imageprovider.hpp" #include "imageprovider.hpp"
#include <qcontainerfwd.h>
#include <qdebug.h> #include <qdebug.h>
#include <qimage.h> #include <qimage.h>
#include <qlogging.h> #include <qlogging.h>
@ -7,10 +8,14 @@
#include <qobject.h> #include <qobject.h>
#include <qpixmap.h> #include <qpixmap.h>
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qtypes.h>
namespace { namespace {
namespace {
QMap<QString, QsImageHandle*> liveImages; // NOLINT QMap<QString, QsImageHandle*> liveImages; // NOLINT
quint32 handleIndex = 0; // NOLINT
} // namespace
void parseReq(const QString& req, QString& target, QString& param) { void parseReq(const QString& req, QString& target, QString& param) {
auto splitIdx = req.indexOf('/'); auto splitIdx = req.indexOf('/');
@ -24,14 +29,9 @@ void parseReq(const QString& req, QString& target, QString& param) {
} // namespace } // namespace
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent) QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type)
: QObject(parent) : type(type)
, type(type) { , id(QString::number(++handleIndex)) {
{
auto dbg = QDebug(&this->id);
dbg.nospace() << static_cast<void*>(this);
}
liveImages.insert(this->id, this); liveImages.insert(this->id, this);
} }
@ -85,3 +85,9 @@ QsPixmapProvider::requestPixmap(const QString& id, QSize* size, const QSize& req
return QPixmap(); 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; QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
}; };
class QsImageHandle: public QObject { class QsImageHandle {
Q_OBJECT;
public: public:
explicit QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent = nullptr); explicit QsImageHandle(QQmlImageProviderBase::ImageType type);
~QsImageHandle() override; virtual ~QsImageHandle();
Q_DISABLE_COPY_MOVE(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 QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
virtual QPixmap requestPixmap(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; QQmlImageProviderBase::ImageType type;
QString id; 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", "qsmenuanchor.hpp",
"clock.hpp", "clock.hpp",
"scriptmodel.hpp", "scriptmodel.hpp",
"colorquantizer.hpp",
] ]
----- -----

View file

@ -42,6 +42,24 @@ QString QuickshellScreenInfo::name() const {
return this->screen->name(); 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 { qint32 QuickshellScreenInfo::x() const {
if (this->screen == nullptr) { if (this->screen == nullptr) {
this->warnDangling(); this->warnDangling();

View file

@ -29,6 +29,10 @@ class QuickshellScreenInfo: public QObject {
/// ///
/// Usually something like `DP-1`, `HDMI-1`, `eDP-1`. /// Usually something like `DP-1`, `HDMI-1`, `eDP-1`.
Q_PROPERTY(QString name READ name CONSTANT); 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 x READ x NOTIFY geometryChanged);
Q_PROPERTY(qint32 y READ y NOTIFY geometryChanged); Q_PROPERTY(qint32 y READ y NOTIFY geometryChanged);
Q_PROPERTY(qint32 width READ width NOTIFY geometryChanged); Q_PROPERTY(qint32 width READ width NOTIFY geometryChanged);
@ -49,6 +53,8 @@ public:
bool operator==(QuickshellScreenInfo& other) const; bool operator==(QuickshellScreenInfo& other) const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString model() const;
[[nodiscard]] QString serialNumber() const;
[[nodiscard]] qint32 x() const; [[nodiscard]] qint32 x() const;
[[nodiscard]] qint32 y() const; [[nodiscard]] qint32 y() const;
[[nodiscard]] qint32 width() const; [[nodiscard]] qint32 width() const;

View file

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

View file

@ -30,7 +30,17 @@ namespace qs::dbus::dbusmenu {
using menu::QsMenuEntry; using menu::QsMenuEntry;
class DBusMenu; 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.
/// Menu item shared by an external program via the /// Menu item shared by an external program via the
@ -93,7 +103,7 @@ private:
bool visible = true; bool visible = true;
bool mSeparator = false; bool mSeparator = false;
QString iconName; QString iconName;
DBusMenuPngImage* image = nullptr; DBusMenuPngImage image;
menu::QsMenuButtonType::Enum mButtonType = menu::QsMenuButtonType::None; menu::QsMenuButtonType::Enum mButtonType = menu::QsMenuButtonType::None;
Qt::CheckState mCheckState = Qt::Unchecked; Qt::CheckState mCheckState = Qt::Unchecked;
bool displayChildren = false; bool displayChildren = false;
@ -156,17 +166,6 @@ private:
QDebug operator<<(QDebug debug, DBusMenu* menu); 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; class DBusMenuHandle;
QDebug operator<<(QDebug debug, const DBusMenuHandle* handle); QDebug operator<<(QDebug debug, const DBusMenuHandle* handle);

View file

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

View file

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

View file

@ -21,16 +21,17 @@ namespace qs::io::ipc::comm {
struct NoCurrentGeneration: std::monostate {}; struct NoCurrentGeneration: std::monostate {};
struct TargetNotFound: std::monostate {}; struct TargetNotFound: std::monostate {};
struct FunctionNotFound: std::monostate {}; struct EntryNotFound: std::monostate {};
using QueryResponse = std::variant< using QueryResponse = std::variant<
std::monostate, std::monostate,
NoCurrentGeneration, NoCurrentGeneration,
TargetNotFound, TargetNotFound,
FunctionNotFound, EntryNotFound,
QVector<WireTargetDefinition>, QVector<WireTargetDefinition>,
WireTargetDefinition, WireTargetDefinition,
WireFunctionDefinition>; WireFunctionDefinition,
WirePropertyDefinition>;
void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const { void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto resp = conn->responseStream<QueryResponse>(); auto resp = conn->responseStream<QueryResponse>();
@ -44,16 +45,24 @@ void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto* handler = registry->findHandler(this->target); auto* handler = registry->findHandler(this->target);
if (handler) { if (handler) {
if (this->function.isEmpty()) { if (this->name.isEmpty()) {
resp << handler->wireDef(); resp << handler->wireDef();
} else { } else {
auto* func = handler->findFunction(this->function); auto* func = handler->findFunction(this->name);
if (func) { if (func) {
resp << func->wireDef(); resp << func->wireDef();
} else { return;
resp << FunctionNotFound();
} }
auto* prop = handler->findProperty(this->name);
if (prop) {
resp << prop->wireDef();
return;
}
resp << EntryNotFound();
} }
} else { } else {
resp << TargetNotFound(); resp << TargetNotFound();
@ -64,8 +73,8 @@ void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
} }
} }
int queryMetadata(IpcClient* client, const QString& target, const QString& function) { int queryMetadata(IpcClient* client, const QString& target, const QString& name) {
client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .function = function})); client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .name = name}));
QueryResponse slot; QueryResponse slot;
if (!client->waitForResponse(slot)) return -1; 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(); qCInfo(logBare).noquote() << std::get<WireTargetDefinition>(slot).toString();
} else if (std::holds_alternative<WireFunctionDefinition>(slot)) { } else if (std::holds_alternative<WireFunctionDefinition>(slot)) {
qCInfo(logBare).noquote() << std::get<WireFunctionDefinition>(slot).toString(); 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)) { } else if (std::holds_alternative<TargetNotFound>(slot)) {
qCCritical(logBare) << "Target not found."; qCCritical(logBare) << "Target not found.";
} else if (std::holds_alternative<FunctionNotFound>(slot)) { } else if (std::holds_alternative<EntryNotFound>(slot)) {
qCCritical(logBare) << "Function not found."; qCCritical(logBare) << "Function not found.";
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) { } else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
qCCritical(logBare) << "Not ready to accept queries yet."; qCCritical(logBare) << "Not ready to accept queries yet.";
@ -119,7 +130,7 @@ using StringCallResponse = std::variant<
std::monostate, std::monostate,
NoCurrentGeneration, NoCurrentGeneration,
TargetNotFound, TargetNotFound,
FunctionNotFound, EntryNotFound,
ArgParseFailed, ArgParseFailed,
Completed>; Completed>;
@ -137,7 +148,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
auto* func = handler->findFunction(this->function); auto* func = handler->findFunction(this->function);
if (!func) { if (!func) {
resp << FunctionNotFound(); resp << EntryNotFound();
return; return;
} }
@ -223,7 +234,7 @@ int callFunction(
qCCritical(logBare).noquote() << "Function definition:" << error.definition.toString(); qCCritical(logBare).noquote() << "Function definition:" << error.definition.toString();
} else if (std::holds_alternative<TargetNotFound>(slot)) { } else if (std::holds_alternative<TargetNotFound>(slot)) {
qCCritical(logBare) << "Target not found."; qCCritical(logBare) << "Target not found.";
} else if (std::holds_alternative<FunctionNotFound>(slot)) { } else if (std::holds_alternative<EntryNotFound>(slot)) {
qCCritical(logBare) << "Function not found."; qCCritical(logBare) << "Function not found.";
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) { } else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
qCCritical(logBare) << "Not ready to accept queries yet."; qCCritical(logBare) << "Not ready to accept queries yet.";
@ -233,4 +244,74 @@ int callFunction(
return -1; 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 } // namespace qs::io::ipc::comm

View file

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

View file

@ -107,6 +107,32 @@ WireFunctionDefinition IpcFunction::wireDef() const {
return wire; 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) { IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
for (const auto& arg: function.argumentTypes) { for (const auto& arg: function.argumentTypes) {
this->argumentSlots.emplace_back(arg); 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->complete = true;
this->updateRegistration(); this->updateRegistration();
@ -270,6 +311,10 @@ WireTargetDefinition IpcHandler::wireDef() const {
wire.functions += func.wireDef(); wire.functions += func.wireDef();
} }
for (const auto& prop: this->propertyMap.values()) {
wire.properties += prop.wireDef();
}
return wire; return wire;
} }
@ -307,6 +352,13 @@ IpcFunction* IpcHandler::findFunction(const QString& name) {
else return &*itr; 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) { IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
return this->handlers.value(target); return this->handlers.value(target);
} }

View file

@ -53,14 +53,28 @@ private:
friend class IpcFunction; 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; class IpcHandlerRegistry;
///! Handler for IPC message calls. ///! Handler for IPC message calls.
/// Each IpcHandler is registered into a per-instance map by its unique @@target. /// 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 /// #### 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. /// 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 /// **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 /// ```sh
/// $ qs msg -s /// $ qs ipc show
/// target rect /// target rect
/// function setColor(color: color): void /// function setColor(color: color): void
/// function getColor(): color /// function getColor(): color
@ -124,18 +138,22 @@ class IpcHandlerRegistry;
/// function getRadius(): int /// function getRadius(): int
/// ``` /// ```
/// ///
/// and then invoked using `qs msg`. /// and then invoked using `qs ipc call`.
/// ```sh /// ```sh
/// $ qs msg rect setColor orange /// $ qs ipc call rect setColor orange
/// $ qs msg rect setAngle 40.5 /// $ qs ipc call rect setAngle 40.5
/// $ qs msg rect setRadius 30 /// $ qs ipc call rect setRadius 30
/// $ qs msg rect getColor /// $ qs ipc call rect getColor
/// #ffffa500 /// #ffffa500
/// $ qs msg rect getAngle /// $ qs ipc call rect getAngle
/// 40.5 /// 40.5
/// $ qs msg rect getRadius /// $ qs ipc call rect getRadius
/// 30 /// 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 class IpcHandler
: public QObject : public QObject
, public PostReloadHook { , public PostReloadHook {
@ -162,12 +180,16 @@ public:
QString listMembers(qsizetype indent); QString listMembers(qsizetype indent);
[[nodiscard]] IpcFunction* findFunction(const QString& name); [[nodiscard]] IpcFunction* findFunction(const QString& name);
[[nodiscard]] IpcProperty* findProperty(const QString& name);
[[nodiscard]] WireTargetDefinition wireDef() const; [[nodiscard]] WireTargetDefinition wireDef() const;
signals: signals:
void enabledChanged(); void enabledChanged();
void targetChanged(); void targetChanged();
private slots:
//void handleIpcPropertyChange();
private: private:
void updateRegistration(bool destroying = false); void updateRegistration(bool destroying = false);
@ -183,6 +205,7 @@ private:
bool complete = false; bool complete = false;
QHash<QString, IpcFunction> functionMap; QHash<QString, IpcFunction> functionMap;
QHash<QString, IpcProperty> propertyMap;
friend class IpcHandlerRegistry; friend class IpcHandlerRegistry;
}; };

View file

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

View file

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

View file

@ -49,6 +49,7 @@ struct CommandState {
QStringOption path; QStringOption path;
QStringOption manifest; QStringOption manifest;
QStringOption name; QStringOption name;
bool newest = false;
} config; } config;
struct { struct {
@ -67,9 +68,13 @@ struct CommandState {
} output; } output;
struct { 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 target;
QStringOption function; QStringOption name;
std::vector<QStringOption> arguments; std::vector<QStringOption> arguments;
} ipc; } ipc;

View file

@ -16,7 +16,7 @@ int parseCommand(int argc, char** argv, CommandState& state) {
.argv = argv, .argv = argv,
}; };
auto addConfigSelection = [&](CLI::App* cmd) { auto addConfigSelection = [&](CLI::App* cmd, bool withNewestOption = false) {
auto* group = cmd->add_option_group("Config Selection") auto* group = cmd->add_option_group("Config Selection")
->description("If no options in this group are specified,\n" ->description("If no options in this group are specified,\n"
"$XDG_CONFIG_HOME/quickshell/shell.qml will be used."); "$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.") "otherwise it is the name of a folder in $XDG_CONFIG_HOME/quickshell.")
->envname("QS_CONFIG_NAME"); ->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; 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."); ->description("Rules to apply to the log being read, in the format of QT_LOGGING_RULES.");
auto* instance = addInstanceSelection(sub)->excludes(file); auto* instance = addInstanceSelection(sub)->excludes(file);
addConfigSelection(sub)->excludes(instance)->excludes(file); addConfigSelection(sub, true)->excludes(instance)->excludes(file);
addLoggingOptions(sub, false); addLoggingOptions(sub, false);
state.subcommand.log = sub; 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."); 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); addLoggingOptions(sub, false, true);
state.subcommand.list = sub; 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."); auto* sub = cli->add_subcommand("kill", "Kill quickshell instances.");
//sub->add_flag("-a,--all", "Kill all matching instances instead of just one."); //sub->add_flag("-a,--all", "Kill all matching instances instead of just one.");
auto* instance = addInstanceSelection(sub); auto* instance = addInstanceSelection(sub);
addConfigSelection(sub)->excludes(instance); addConfigSelection(sub, true)->excludes(instance);
addLoggingOptions(sub, false, true); addLoggingOptions(sub, false, true);
state.subcommand.kill = sub; state.subcommand.kill = sub;
} }
{ {
auto* sub = cli->add_subcommand("msg", "Send messages to IpcHandlers.")->require_option(); auto* sub = cli->add_subcommand("ipc", "Communicate with other Quickshell instances.")
->require_subcommand();
auto* target = sub->add_option("target", state.ipc.target, "The target to message."); state.ipc.ipc = sub;
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* instance = addInstanceSelection(sub); auto* instance = addInstanceSelection(sub);
addConfigSelection(sub)->excludes(instance); addConfigSelection(sub, true)->excludes(instance);
addLoggingOptions(sub, false, true); 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; state.subcommand.msg = sub;
} }

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#include <utility>
#include <qdbusargument.h> #include <qdbusargument.h>
#include <qimage.h> #include <qimage.h>
#include <qobject.h> #include <qobject.h>
@ -23,14 +21,22 @@ struct DBusNotificationImage {
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationImage& pixmap); const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationImage& pixmap);
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationImage& pixmap); const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationImage& pixmap);
class NotificationImage: public QsImageHandle { class NotificationImage: public QsIndexedImageHandle {
public: public:
explicit NotificationImage(DBusNotificationImage image, QObject* parent) explicit NotificationImage(): QsIndexedImageHandle(QQuickAsyncImageProvider::Image) {}
: QsImageHandle(QQuickAsyncImageProvider::Image, parent)
, image(std::move(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; QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
private:
DBusNotificationImage image; DBusNotificationImage image;
}; };
} // namespace qs::service::notifications } // namespace qs::service::notifications

View file

@ -1,5 +1,4 @@
#include "notification.hpp" #include "notification.hpp"
#include <utility>
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
#include <qdbusargument.h> #include <qdbusargument.h>
@ -117,13 +116,12 @@ void Notification::updateProperties(
QString imagePath; QString imagePath;
if (!imageDataName.isEmpty()) { if (imageDataName.isEmpty()) {
this->mImagePixmap.clear();
} else {
auto value = hints.value(imageDataName).value<QDBusArgument>(); auto value = hints.value(imageDataName).value<QDBusArgument>();
DBusNotificationImage image; value >> this->mImagePixmap.writeImage();
value >> image; imagePath = this->mImagePixmap.url();
if (this->mImagePixmap) this->mImagePixmap->deleteLater();
this->mImagePixmap = new NotificationImage(std::move(image), this);
imagePath = this->mImagePixmap->url();
} }
// don't store giant byte arrays longer than necessary // don't store giant byte arrays longer than necessary
@ -131,7 +129,7 @@ void Notification::updateProperties(
hints.remove("image_data"); hints.remove("image_data");
hints.remove("icon_data"); hints.remove("icon_data");
if (!this->mImagePixmap) { if (!this->mImagePixmap.hasData()) {
QString imagePathName; QString imagePathName;
if (hints.contains("image-path")) imagePathName = "image-path"; if (hints.contains("image-path")) imagePathName = "image-path";
else 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/retainable.hpp"
#include "../../core/util.hpp" #include "../../core/util.hpp"
#include "dbusimage.hpp"
namespace qs::service::notifications { namespace qs::service::notifications {
class NotificationImage;
///! The urgency level of a Notification. ///! The urgency level of a Notification.
/// See @@Notification.urgency. /// See @@Notification.urgency.
class NotificationUrgency: public QObject { class NotificationUrgency: public QObject {
@ -187,7 +186,7 @@ private:
quint32 mId; quint32 mId;
NotificationCloseReason::Enum mCloseReason = NotificationCloseReason::Dismissed; NotificationCloseReason::Enum mCloseReason = NotificationCloseReason::Dismissed;
bool mLastGeneration = false; bool mLastGeneration = false;
NotificationImage* mImagePixmap = nullptr; NotificationImage mImagePixmap;
QList<NotificationAction*> mActions; QList<NotificationAction*> mActions;
// clang-format off // clang-format off

View file

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

View file

@ -34,7 +34,7 @@
This protocol exposes hyprland-specific wl_surface properties. This protocol exposes hyprland-specific wl_surface properties.
</description> </description>
<interface name="hyprland_surface_manager_v1" version="1"> <interface name="hyprland_surface_manager_v1" version="2">
<description summary="manager for hyprland surface objects"> <description summary="manager for hyprland surface objects">
This interface allows a client to create hyprland surface objects. This interface allows a client to create hyprland surface objects.
</description> </description>
@ -63,7 +63,7 @@
</enum> </enum>
</interface> </interface>
<interface name="hyprland_surface_v1" version="1"> <interface name="hyprland_surface_v1" version="2">
<description summary="hyprland-specific wl_surface properties"> <description summary="hyprland-specific wl_surface properties">
This interface allows access to hyprland-specific properties of a wl_surface. 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="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)"/> <entry name="out_of_range" value="1" summary="given opacity was not in the range 0.0 - 1.0 (inclusive)"/>
</enum> </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> </interface>
</protocol> </protocol>

View file

@ -7,13 +7,13 @@
namespace qs::hyprland::surface::impl { namespace qs::hyprland::surface::impl {
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(1) { HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(2) {
this->initialize(); this->initialize();
} }
HyprlandSurface* HyprlandSurface*
HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) { 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() { HyprlandSurfaceManager* HyprlandSurfaceManager::instance() {

View file

@ -1,17 +1,20 @@
#include "qml.hpp" #include "qml.hpp"
#include <memory> #include <memory>
#include <private/qhighdpiscaling_p.h>
#include <private/qwaylandwindow_p.h> #include <private/qwaylandwindow_p.h>
#include <qlogging.h> #include <qlogging.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlinfo.h> #include <qqmlinfo.h>
#include <qregion.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include <qvariant.h>
#include <qwindow.h> #include <qwindow.h>
#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp" #include "../../../window/proxywindow.hpp"
#include "../../../window/windowinterface.hpp" #include "../../../window/windowinterface.hpp"
#include "../../util.hpp"
#include "manager.hpp" #include "manager.hpp"
#include "surface.hpp" #include "surface.hpp"
@ -40,6 +43,15 @@ HyprlandWindow::HyprlandWindow(ProxyWindowBase* window): QObject(nullptr), proxy
&HyprlandWindow::onWindowConnected &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); QObject::connect(window, &QObject::destroyed, this, &HyprlandWindow::onProxyWindowDestroyed);
if (window->backingWindow()) { if (window->backingWindow()) {
@ -60,14 +72,76 @@ void HyprlandWindow::setOpacity(qreal opacity) {
this->mOpacity = opacity; this->mOpacity = opacity;
if (this->surface) { if (this->surface && this->proxyWindow) {
this->surface->setOpacity(opacity); this->pendingPolish.opacity = true;
qs::wayland::util::scheduleCommit(this->proxyWindow); this->proxyWindow->schedulePolish();
} }
emit this->opacityChanged(); 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() { void HyprlandWindow::onWindowConnected() {
this->mWindow = this->proxyWindow->backingWindow(); this->mWindow = this->proxyWindow->backingWindow();
// disconnected by destructor // disconnected by destructor
@ -86,11 +160,24 @@ void HyprlandWindow::onWindowVisibleChanged() {
if (!this->mWindow->handle()) { if (!this->mWindow->handle()) {
this->mWindow->create(); 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) { if (this->mWaylandWindow) {
// disconnected by destructor QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
}
this->mWaylandWindow = window;
if (!window) return;
QObject::connect(
this->mWaylandWindow,
&QObject::destroyed,
this,
&HyprlandWindow::onWaylandWindowDestroyed
);
QObject::connect( QObject::connect(
this->mWaylandWindow, this->mWaylandWindow,
@ -109,10 +196,10 @@ void HyprlandWindow::onWindowVisibleChanged() {
if (this->mWaylandWindow->surface()) { if (this->mWaylandWindow->surface()) {
this->onWaylandSurfaceCreated(); this->onWaylandSurfaceCreated();
} }
}
}
} }
void HyprlandWindow::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }
void HyprlandWindow::onWaylandSurfaceCreated() { void HyprlandWindow::onWaylandSurfaceCreated() {
auto* manager = impl::HyprlandSurfaceManager::instance(); auto* manager = impl::HyprlandSurfaceManager::instance();
@ -122,12 +209,26 @@ void HyprlandWindow::onWaylandSurfaceCreated() {
return; return;
} }
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->surface) {
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow); auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
this->surface = std::unique_ptr<impl::HyprlandSurface>(ext); this->surface = std::unique_ptr<impl::HyprlandSurface>(ext);
}
if (this->mOpacity != 1.0) { this->mWaylandWindow->setProperty("hyprland_window_ext", QVariant::fromValue(this));
this->surface->setOpacity(this->mOpacity);
qs::wayland::util::scheduleCommit(this->proxyWindow); 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 // 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. // hyprland_surface_v1 and wl_surface objects.
if (this->surface == nullptr) {
this->proxyWindow = nullptr; this->proxyWindow = nullptr;
if (this->surface == nullptr) {
this->deleteLater(); this->deleteLater();
} }
} }

View file

@ -9,6 +9,7 @@
#include <qtypes.h> #include <qtypes.h>
#include <qwindow.h> #include <qwindow.h>
#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp" #include "../../../window/proxywindow.hpp"
#include "surface.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 /// [hyprland-surface-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-surface-v1.xml
class HyprlandWindow: public QObject { class HyprlandWindow: public QObject {
Q_OBJECT; 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 /// 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. /// both the window content *and* visual effects such as blur that apply to it.
/// ///
/// Default: 1.0 /// Default: 1.0
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged); 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_ELEMENT;
QML_UNCREATABLE("HyprlandWindow can only be used as an attached object."); QML_UNCREATABLE("HyprlandWindow can only be used as an attached object.");
QML_ATTACHED(HyprlandWindow); QML_ATTACHED(HyprlandWindow);
@ -48,17 +56,25 @@ public:
[[nodiscard]] qreal opacity() const; [[nodiscard]] qreal opacity() const;
void setOpacity(qreal opacity); void setOpacity(qreal opacity);
[[nodiscard]] PendingRegion* visibleMask() const;
virtual void setVisibleMask(PendingRegion* mask);
static HyprlandWindow* qmlAttachedProperties(QObject* object); static HyprlandWindow* qmlAttachedProperties(QObject* object);
signals: signals:
void opacityChanged(); void opacityChanged();
void visibleMaskChanged();
private slots: private slots:
void onWindowConnected(); void onWindowConnected();
void onWindowVisibleChanged(); void onWindowVisibleChanged();
void onWaylandWindowDestroyed();
void onWaylandSurfaceCreated(); void onWaylandSurfaceCreated();
void onWaylandSurfaceDestroyed(); void onWaylandSurfaceDestroyed();
void onProxyWindowDestroyed(); void onProxyWindowDestroyed();
void onVisibleMaskDestroyed();
void onWindowPolished();
void updateVisibleMask();
private: private:
void disconnectWaylandWindow(); void disconnectWaylandWindow();
@ -67,7 +83,13 @@ private:
QWindow* mWindow = nullptr; QWindow* mWindow = nullptr;
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
struct {
bool opacity : 1 = false;
bool visibleMask : 1 = false;
} pendingPolish;
qreal mOpacity = 1.0; qreal mOpacity = 1.0;
PendingRegion* mVisibleMask = nullptr;
std::unique_ptr<impl::HyprlandSurface> surface; std::unique_ptr<impl::HyprlandSurface> surface;
}; };

View file

@ -1,19 +1,53 @@
#include "surface.hpp" #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 <qtypes.h>
#include <qwayland-hyprland-surface-v1.h> #include <qwayland-hyprland-surface-v1.h>
#include <wayland-client-protocol.h>
#include <wayland-hyprland-surface-v1-client-protocol.h> #include <wayland-hyprland-surface-v1-client-protocol.h>
#include <wayland-util.h> #include <wayland-util.h>
namespace qs::hyprland::surface::impl { namespace qs::hyprland::surface::impl {
HyprlandSurface::HyprlandSurface(::hyprland_surface_v1* surface) HyprlandSurface::HyprlandSurface(
: QtWayland::hyprland_surface_v1(surface) {} ::hyprland_surface_v1* surface,
QtWaylandClient::QWaylandWindow* backer
)
: QtWayland::hyprland_surface_v1(surface)
, backer(backer)
, backerSurface(backer->surface()) {}
HyprlandSurface::~HyprlandSurface() { this->destroy(); } HyprlandSurface::~HyprlandSurface() { this->destroy(); }
bool HyprlandSurface::surfaceEq(wl_surface* surface) const {
return surface == this->backerSurface;
}
void HyprlandSurface::setOpacity(qreal opacity) { void HyprlandSurface::setOpacity(qreal opacity) {
this->set_opacity(wl_fixed_from_double(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 } // namespace qs::hyprland::surface::impl

View file

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

View file

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

View file

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

View file

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