forked from quickshell/quickshell
Compare commits
10 commits
3c7dfcb220
...
f17bc07da3
Author | SHA1 | Date | |
---|---|---|---|
|
f17bc07da3 | ||
|
4f2610dece | ||
|
9417d6fa57 | ||
|
420529362f | ||
|
325be8857c | ||
|
b289bfa504 | ||
|
cdaff2967f | ||
|
c6791cf1f2 | ||
|
b73eff0e47 | ||
![]() |
6a017d63d6 |
src
core
CMakeLists.txtclock.cppclock.hppcolorquantizer.cppcolorquantizer.hppdesktopentry.cppimageprovider.cppimageprovider.hppmodule.mdqmlscreen.cppqmlscreen.hpp
dbus/dbusmenu
io
ipc
launch
services
wayland
hyprland/surface
wlr_layershell.cppwindow
|
@ -37,6 +37,7 @@ qt_add_library(quickshell-core STATIC
|
|||
common.cpp
|
||||
iconprovider.cpp
|
||||
scriptmodel.cpp
|
||||
colorquantizer.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-core
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
182
src/core/colorquantizer.cpp
Normal 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);
|
||||
}
|
95
src/core/colorquantizer.hpp
Normal file
95
src/core/colorquantizer.hpp
Normal 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();
|
||||
};
|
|
@ -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()) {
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -29,5 +29,6 @@ headers = [
|
|||
"qsmenuanchor.hpp",
|
||||
"clock.hpp",
|
||||
"scriptmodel.hpp",
|
||||
"colorquantizer.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -282,7 +282,7 @@ void StatusNotifierItem::onGetAllFailed() const {
|
|||
}
|
||||
|
||||
TrayImageHandle::TrayImageHandle(StatusNotifierItem* item)
|
||||
: QsImageHandle(QQmlImageProviderBase::Pixmap, item)
|
||||
: QsImageHandle(QQmlImageProviderBase::Pixmap)
|
||||
, item(item) {}
|
||||
|
||||
QPixmap
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue