forked from quickshell/quickshell
Compare commits
31 commits
66b9917e70
...
4f2610dece
Author | SHA1 | Date | |
---|---|---|---|
|
4f2610dece | ||
|
9417d6fa57 | ||
|
420529362f | ||
|
325be8857c | ||
|
b289bfa504 | ||
|
cdaff2967f | ||
|
c6791cf1f2 | ||
|
b73eff0e47 | ||
![]() |
6a017d63d6 | ||
|
3c7dfcb220 | ||
|
b336129c34 | ||
|
bc73d35d03 | ||
|
6464ead0f1 | ||
|
d6b58521e9 | ||
|
d195ca7680 | ||
|
ca79715cce | ||
|
c2ed5bf559 | ||
|
6024c37492 | ||
|
6d8022b709 | ||
![]() |
8b6aa624a2 | ||
|
cd429142a4 | ||
|
918dd2392d | ||
|
2c411fce5a | ||
|
26d443aa50 | ||
|
af86d5fd19 | ||
|
761d99d644 | ||
|
fca058e66c | ||
|
eaf854935b | ||
|
f3b7171b25 | ||
|
dc3a79600d | ||
|
47bcf8ee61 |
.clang-tidy
.github/workflows
BUILD.mdCMakeLists.txtJustfileci
default.nixflake.locksrc
core
clock.cppclock.hppdesktopentry.cppiconimageprovider.cppimageprovider.cppimageprovider.hppmodule.mdpaths.cppplugin.cpppopupanchor.cppqmlscreen.cppqmlscreen.hppregion.cppregion.hppscriptmodel.hppstacklist.hpp
test
variants.cppvariants.hppcrash
dbus
debug
io
ipc
launch
services
greetd
mpris
notifications
pipewire
core.cppcore.hppdefaults.cppdevice.cpplink.cppmetadata.cppnode.cppnode.hppqml.cppqml.hppregistry.cppregistry.hpp
status_notifier
upower
wayland
|
@ -7,6 +7,7 @@ Checks: >
|
|||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-return-const-ref-from-parameter,
|
||||
concurrency-*,
|
||||
cppcoreguidelines-*,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
|
@ -44,6 +45,7 @@ Checks: >
|
|||
-readability-container-data-pointer,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-avoid-nested-conditional-operator,
|
||||
-readability-math-missing-parentheses,
|
||||
tidyfox-*,
|
||||
CheckOptions:
|
||||
performance-for-range-copy.WarnOnAllAutoCopies: true
|
||||
|
|
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
@ -6,7 +6,7 @@ jobs:
|
|||
name: Nix
|
||||
strategy:
|
||||
matrix:
|
||||
qtver: [qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
||||
qtver: [qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
||||
compiler: [clang, gcc]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -43,6 +43,7 @@ jobs:
|
|||
qt6-shadertools \
|
||||
wayland-protocols \
|
||||
wayland \
|
||||
libdrm \
|
||||
libxcb \
|
||||
libpipewire \
|
||||
cli11 \
|
||||
|
|
18
BUILD.md
18
BUILD.md
|
@ -130,6 +130,24 @@ which allows quickshell to be used as a session lock under compatible wayland co
|
|||
|
||||
To disable: `-DWAYLAND_TOPLEVEL_MANAGEMENT=OFF`
|
||||
|
||||
#### Screencopy
|
||||
Enables streaming video from monitors and toplevel windows through various protocols.
|
||||
|
||||
To disable: `-DSCREENCOPY=OFF`
|
||||
|
||||
Dependencies:
|
||||
- `libdrm`
|
||||
- `libgbm`
|
||||
|
||||
Specific protocols can also be disabled:
|
||||
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
||||
- `DSCREENCOPY_WLR=OFF` - Disable screencopy via [zwlr-screencopy-v1]
|
||||
- `DSCREENCOPY_HYPRLAND_TOPLEVEL=OFF` - Disable screencopy via [hyprland-toplevel-export-v1]
|
||||
|
||||
[ext-image-copy-capture-v1]:https://wayland.app/protocols/ext-image-copy-capture-v1
|
||||
[zwlr-screencopy-v1]: https://wayland.app/protocols/wlr-screencopy-unstable-v1
|
||||
[hyprland-toplevel-export-v1]: https://wayland.app/protocols/hyprland-toplevel-export-v1
|
||||
|
||||
### X11
|
||||
This feature enables x11 support. Currently this implements panel windows for X11 similarly
|
||||
to the wlroots layershell above.
|
||||
|
|
|
@ -56,6 +56,10 @@ boption(HYPRLAND_IPC " Hyprland IPC" ON REQUIRES HYPRLAND)
|
|||
boption(HYPRLAND_GLOBAL_SHORTCUTS " Hyprland Global Shortcuts" ON REQUIRES HYPRLAND)
|
||||
boption(HYPRLAND_FOCUS_GRAB " Hyprland Focus Grabbing" ON REQUIRES HYPRLAND)
|
||||
boption(HYPRLAND_SURFACE_EXTENSIONS " Hyprland Surface Extensions" ON REQUIRES HYPRLAND)
|
||||
boption(SCREENCOPY " Screencopy" ON REQUIRES WAYLAND)
|
||||
boption(SCREENCOPY_ICC " Image Copy Capture" ON REQUIRES WAYLAND)
|
||||
boption(SCREENCOPY_WLR " Wlroots Screencopy" ON REQUIRES WAYLAND)
|
||||
boption(SCREENCOPY_HYPRLAND_TOPLEVEL " Hyprland Toplevel Export" ON REQUIRES WAYLAND)
|
||||
boption(X11 "X11" ON)
|
||||
boption(I3 "I3/Sway" ON)
|
||||
boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3)
|
||||
|
@ -70,7 +74,7 @@ boption(SERVICE_NOTIFICATIONS "Notifications" ON)
|
|||
include(cmake/install-qml-module.cmake)
|
||||
include(cmake/util.cmake)
|
||||
|
||||
add_compile_options(-Wall -Wextra)
|
||||
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
|
||||
|
||||
# pipewire defines this, breaking PCH
|
||||
add_compile_definitions(_REENTRANT)
|
||||
|
|
6
Justfile
6
Justfile
|
@ -4,13 +4,13 @@ fmt:
|
|||
find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i
|
||||
|
||||
lint:
|
||||
find src -type f -name "*.cpp" -print0 | parallel -q0 --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
|
||||
lint-ci:
|
||||
find src -type f -name "*.cpp" -print0 | parallel -q0 --no-notice --will-cite --tty clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
|
||||
lint-changed:
|
||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
|
||||
configure target='debug' *FLAGS='':
|
||||
cmake -GNinja -B {{builddir}} \
|
||||
|
|
|
@ -11,9 +11,14 @@ in {
|
|||
# For old qt versions, grab the commit before the version bump that has all the patches
|
||||
# instead of the bumped version.
|
||||
|
||||
qt6_8_1 = byCommit {
|
||||
commit = "3df3c47c19dc90fec35359e89ffb52b34d2b0e94";
|
||||
sha256 = "1lhlm7czhwwys5ak6ngb5li6bxddilb9479k9nkss502kw8hwjyz";
|
||||
};
|
||||
|
||||
qt6_8_0 = byCommit {
|
||||
commit = "23e89b7da85c3640bbc2173fe04f4bd114342367";
|
||||
sha256 = "1b2v6y3bja4br5ribh9lj6xzz2k81dggz708b2mib83rwb509wyb";
|
||||
commit = "352f462ad9d2aa2cde75fdd8f1734e86402a3ff6";
|
||||
sha256 = "02zfgkr9fpd6iwfh6dcr3m6fnx61jppm3v081f3brvkqwmmz7zq1";
|
||||
};
|
||||
|
||||
qt6_7_3 = byCommit {
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
jemalloc,
|
||||
wayland,
|
||||
wayland-protocols,
|
||||
libdrm,
|
||||
libgbm ? null,
|
||||
xorg,
|
||||
pipewire,
|
||||
pam,
|
||||
|
@ -64,7 +66,7 @@
|
|||
++ lib.optional withCrashReporter breakpad
|
||||
++ lib.optional withJemalloc jemalloc
|
||||
++ lib.optional withQtSvg qt6.qtsvg
|
||||
++ lib.optionals withWayland [ qt6.qtwayland wayland ]
|
||||
++ lib.optionals withWayland ([ qt6.qtwayland wayland ] ++ (if libgbm != null then [ libdrm libgbm ] else []))
|
||||
++ lib.optional withX11 xorg.libxcb
|
||||
++ lib.optional withPam pam
|
||||
++ lib.optional withPipewire pipewire;
|
||||
|
@ -79,6 +81,7 @@
|
|||
(lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
|
||||
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
|
||||
(lib.cmakeBool "WAYLAND" withWayland)
|
||||
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
|
||||
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
|
||||
(lib.cmakeBool "SERVICE_PAM" withPam)
|
||||
(lib.cmakeBool "HYPRLAND" withHyprland)
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1732014248,
|
||||
"narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=",
|
||||
"lastModified": 1736012469,
|
||||
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "23e89b7da85c3640bbc2173fe04f4bd114342367",
|
||||
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
#include "model.hpp"
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logDesktopEntry, "quickshell.desktopentry", QtWarningMsg);
|
||||
}
|
||||
|
||||
struct Locale {
|
||||
explicit Locale() = default;
|
||||
|
@ -78,6 +80,7 @@ struct Locale {
|
|||
QString modifier;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
QDebug operator<<(QDebug debug, const Locale& locale) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
|
||||
|
@ -210,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;
|
||||
|
@ -226,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()) {
|
||||
|
@ -306,7 +309,7 @@ void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
|||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
|
||||
for (auto& entry: entries) {
|
||||
if (entry.isDir()) this->scanPath(entry.path(), prefix + dir.dirName() + "-");
|
||||
if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-");
|
||||
else if (entry.isFile()) {
|
||||
auto path = entry.filePath();
|
||||
if (!path.endsWith(".desktop")) {
|
||||
|
@ -314,9 +317,8 @@ void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
|||
continue;
|
||||
}
|
||||
|
||||
auto* file = new QFile(path);
|
||||
|
||||
if (!file->open(QFile::ReadOnly)) {
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCDebug(logDesktopEntry) << "Could not open file" << path;
|
||||
continue;
|
||||
}
|
||||
|
@ -324,7 +326,7 @@ void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
|||
auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8);
|
||||
auto lowerId = id.toLower();
|
||||
|
||||
auto text = QString::fromUtf8(file->readAll());
|
||||
auto text = QString::fromUtf8(file.readAll());
|
||||
auto* dentry = new DesktopEntry(id, this);
|
||||
dentry->parseEntry(text);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "iconimageprovider.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qicon.h>
|
||||
|
@ -49,8 +50,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
|||
QPixmap IconImageProvider::missingPixmap(const QSize& size) {
|
||||
auto width = size.width() % 2 == 0 ? size.width() : size.width() + 1;
|
||||
auto height = size.height() % 2 == 0 ? size.height() : size.height() + 1;
|
||||
if (width < 2) width = 2;
|
||||
if (height < 2) height = 2;
|
||||
width = std::max(width, 2);
|
||||
height = std::max(height, 2);
|
||||
|
||||
auto pixmap = QPixmap(width, height);
|
||||
pixmap.fill(QColorConstants::Black);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "imageprovider.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qimage.h>
|
||||
#include <qlogging.h>
|
||||
|
@ -7,17 +8,30 @@
|
|||
#include <qobject.h>
|
||||
#include <qpixmap.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
static QMap<QString, QsImageHandle*> liveImages; // NOLINT
|
||||
namespace {
|
||||
|
||||
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent)
|
||||
: QObject(parent)
|
||||
, type(type) {
|
||||
{
|
||||
auto dbg = QDebug(&this->id);
|
||||
dbg.nospace() << static_cast<void*>(this);
|
||||
namespace {
|
||||
QMap<QString, QsImageHandle*> liveImages; // NOLINT
|
||||
quint32 handleIndex = 0; // NOLINT
|
||||
} // namespace
|
||||
|
||||
void parseReq(const QString& req, QString& target, QString& param) {
|
||||
auto splitIdx = req.indexOf('/');
|
||||
if (splitIdx != -1) {
|
||||
target = req.sliced(0, splitIdx);
|
||||
param = req.sliced(splitIdx + 1);
|
||||
} else {
|
||||
target = req;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type)
|
||||
: type(type)
|
||||
, id(QString::number(++handleIndex)) {
|
||||
liveImages.insert(this->id, this);
|
||||
}
|
||||
|
||||
|
@ -43,16 +57,6 @@ QPixmap QsImageHandle::
|
|||
return QPixmap();
|
||||
}
|
||||
|
||||
void parseReq(const QString& req, QString& target, QString& param) {
|
||||
auto splitIdx = req.indexOf('/');
|
||||
if (splitIdx != -1) {
|
||||
target = req.sliced(0, splitIdx);
|
||||
param = req.sliced(splitIdx + 1);
|
||||
} else {
|
||||
target = req;
|
||||
}
|
||||
}
|
||||
|
||||
QImage QsImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize) {
|
||||
QString target;
|
||||
QString param;
|
||||
|
@ -81,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;
|
||||
};
|
||||
|
|
|
@ -28,5 +28,6 @@ headers = [
|
|||
"types.hpp",
|
||||
"qsmenuanchor.hpp",
|
||||
"clock.hpp",
|
||||
"scriptmodel.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
#include "instanceinfo.hpp"
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg);
|
||||
}
|
||||
|
||||
QsPaths* QsPaths::instance() {
|
||||
static auto* instance = new QsPaths(); // NOLINT
|
||||
|
|
|
@ -10,16 +10,9 @@ static QVector<QsEnginePlugin*> plugins; // NOLINT
|
|||
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
|
||||
|
||||
void QsEnginePlugin::initPlugins() {
|
||||
plugins.erase(
|
||||
std::remove_if(
|
||||
plugins.begin(),
|
||||
plugins.end(),
|
||||
[](QsEnginePlugin* plugin) { return !plugin->applies(); }
|
||||
),
|
||||
plugins.end()
|
||||
);
|
||||
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
||||
|
||||
std::sort(plugins.begin(), plugins.end(), [](QsEnginePlugin* a, QsEnginePlugin* b) {
|
||||
std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) {
|
||||
return b->dependencies().contains(a->name());
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "popupanchor.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
|
@ -276,9 +277,7 @@ void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool only
|
|||
effectiveX = screenGeometry.right() - windowGeometry.width() + 1;
|
||||
}
|
||||
|
||||
if (effectiveX < screenGeometry.left()) {
|
||||
effectiveX = screenGeometry.left();
|
||||
}
|
||||
effectiveX = std::max(effectiveX, screenGeometry.left());
|
||||
}
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::SlideY)) {
|
||||
|
@ -286,9 +285,7 @@ void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool only
|
|||
effectiveY = screenGeometry.bottom() - windowGeometry.height() + 1;
|
||||
}
|
||||
|
||||
if (effectiveY < screenGeometry.top()) {
|
||||
effectiveY = screenGeometry.top();
|
||||
}
|
||||
effectiveY = std::max(effectiveY, screenGeometry.top());
|
||||
}
|
||||
|
||||
if (adjustment.testFlag(PopupAdjustment::ResizeX)) {
|
||||
|
|
|
@ -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);
|
||||
|
@ -40,7 +44,7 @@ class QuickshellScreenInfo: public QObject {
|
|||
/// The ratio between physical pixels and device-independent (scaled) pixels.
|
||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY physicalPixelDensityChanged);
|
||||
Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged);
|
||||
Q_PROPERTY(Qt::ScreenOrientation primatyOrientation READ primaryOrientation NOTIFY primaryOrientationChanged);
|
||||
Q_PROPERTY(Qt::ScreenOrientation primaryOrientation READ primaryOrientation NOTIFY primaryOrientationChanged);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
|
@ -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;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <qregion.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvectornd.h>
|
||||
|
||||
PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
||||
QObject::connect(this, &PendingRegion::shapeChanged, this, &PendingRegion::changed);
|
||||
|
@ -105,8 +106,19 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
|
|||
return region;
|
||||
}
|
||||
|
||||
QRegion PendingRegion::applyTo(const QRect& rect) const {
|
||||
// if left as the default, dont combine it with the whole rect area, leave it as is.
|
||||
if (this->mIntersection == Intersection::Combine) {
|
||||
return this->build();
|
||||
} else {
|
||||
auto baseRegion = QRegion(rect);
|
||||
return this->applyTo(baseRegion);
|
||||
}
|
||||
}
|
||||
|
||||
void PendingRegion::regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region) {
|
||||
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||
if (!region) return;
|
||||
|
||||
QObject::connect(region, &QObject::destroyed, self, &PendingRegion::onChildDestroyed);
|
||||
QObject::connect(region, &PendingRegion::changed, self, &PendingRegion::childrenChanged);
|
||||
|
|
|
@ -96,6 +96,7 @@ public:
|
|||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] QRegion build() const;
|
||||
[[nodiscard]] QRegion applyTo(QRegion& region) const;
|
||||
[[nodiscard]] QRegion applyTo(const QRect& rect) const;
|
||||
|
||||
RegionShape::Enum mShape = RegionShape::Rect;
|
||||
Intersection::Enum mIntersection = Intersection::Combine;
|
||||
|
@ -109,6 +110,11 @@ signals:
|
|||
void widthChanged();
|
||||
void heightChanged();
|
||||
void childrenChanged();
|
||||
|
||||
/// Triggered when the region's geometry changes.
|
||||
///
|
||||
/// In some cases the region does not update automatically.
|
||||
/// In those cases you can emit this signal manually.
|
||||
void changed();
|
||||
|
||||
private slots:
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
/// delegate: // ...
|
||||
/// }
|
||||
/// ```
|
||||
/// [QAbstractItemModel]: https://doc.qt.io/qt-6/qabstractitemmodel.html
|
||||
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
|
||||
class ScriptModel: public QAbstractListModel {
|
||||
Q_OBJECT;
|
||||
/// The list of values to reflect in the model.
|
||||
|
@ -51,8 +53,19 @@ class ScriptModel: public QAbstractListModel {
|
|||
/// > }
|
||||
/// > ```
|
||||
/// >
|
||||
/// > Note that we are using @@DesktopEntries.values because it will cause @@ScriptModel.values
|
||||
/// > Note that we are using @@ObjectModel.values because it will cause @@ScriptModel.values
|
||||
/// > to receive an update on change.
|
||||
///
|
||||
/// > [!TIP] Most lists exposed by Quickshell are read-only. Some operations like `sort()`
|
||||
/// > act on a list in-place and cannot be used directly on a list exposed by Quickshell.
|
||||
/// > You can copy a list using spread syntax: `[...variable]` instead of `variable`.
|
||||
/// >
|
||||
/// > For example:
|
||||
/// > ```qml
|
||||
/// > ScriptModel {
|
||||
/// > values: [...DesktopEntries.applications.values].sort(...)
|
||||
/// > }
|
||||
/// > ```
|
||||
Q_PROPERTY(QVariantList values READ values WRITE setValues NOTIFY valuesChanged);
|
||||
QML_ELEMENT;
|
||||
|
||||
|
|
173
src/core/stacklist.hpp
Normal file
173
src/core/stacklist.hpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include <qlist.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
template <class T, size_t N>
|
||||
class StackList {
|
||||
public:
|
||||
T& operator[](size_t i) {
|
||||
if (i < N) {
|
||||
return this->array[i];
|
||||
} else {
|
||||
return this->vec[i - N];
|
||||
}
|
||||
}
|
||||
|
||||
const T& operator[](size_t i) const {
|
||||
return const_cast<StackList<T, N>*>(this)->operator[](i); // NOLINT
|
||||
}
|
||||
|
||||
void push(const T& value) {
|
||||
if (this->size < N) {
|
||||
this->array[this->size] = value;
|
||||
} else {
|
||||
this->vec.push_back(value);
|
||||
}
|
||||
|
||||
++this->size;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t length() const { return this->size; }
|
||||
[[nodiscard]] bool isEmpty() const { return this->size == 0; }
|
||||
|
||||
[[nodiscard]] bool operator==(const StackList<T, N>& other) const {
|
||||
if (other.size != this->size) return false;
|
||||
|
||||
for (size_t i = 0; i != this->size; ++i) {
|
||||
if (this->operator[](i) != other[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] QList<T> toList() const {
|
||||
QList<T> list;
|
||||
list.reserve(this->size);
|
||||
|
||||
for (const auto& entry: *this) {
|
||||
list.push_back(entry);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
template <typename Self, typename ListPtr, typename IT>
|
||||
struct BaseIterator {
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using difference_type = int64_t;
|
||||
using value_type = IT;
|
||||
using pointer = IT*;
|
||||
using reference = IT&;
|
||||
|
||||
BaseIterator() = default;
|
||||
explicit BaseIterator(ListPtr list, size_t i): list(list), i(i) {}
|
||||
|
||||
reference operator*() const { return this->list->operator[](this->i); }
|
||||
pointer operator->() const { return &**this; }
|
||||
|
||||
Self& operator++() {
|
||||
++this->i;
|
||||
return *static_cast<Self*>(this);
|
||||
}
|
||||
|
||||
Self& operator--() {
|
||||
--this->i;
|
||||
return *static_cast<Self*>(this);
|
||||
}
|
||||
|
||||
Self operator++(int) {
|
||||
auto v = *this;
|
||||
this->operator++();
|
||||
return v;
|
||||
}
|
||||
|
||||
Self operator--(int) {
|
||||
auto v = *this;
|
||||
this->operator--();
|
||||
return v;
|
||||
}
|
||||
|
||||
difference_type operator-(const Self& other) {
|
||||
return static_cast<int64_t>(this->i) - static_cast<int64_t>(other.i);
|
||||
}
|
||||
|
||||
Self& operator+(difference_type offset) {
|
||||
return Self(this->list, static_cast<int64_t>(this->i) + offset);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const Self& other) const {
|
||||
return this->list == other.list && this->i == other.i;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const Self& other) const { return !(*this == other); }
|
||||
|
||||
private:
|
||||
ListPtr list = nullptr;
|
||||
size_t i = 0;
|
||||
};
|
||||
|
||||
struct Iterator: public BaseIterator<Iterator, StackList<T, N>*, T> {
|
||||
Iterator() = default;
|
||||
Iterator(StackList<T, N>* list, size_t i)
|
||||
: BaseIterator<Iterator, StackList<T, N>*, T>(list, i) {}
|
||||
};
|
||||
|
||||
struct ConstIterator: public BaseIterator<ConstIterator, const StackList<T, N>*, const T> {
|
||||
ConstIterator() = default;
|
||||
ConstIterator(const StackList<T, N>* list, size_t i)
|
||||
: BaseIterator<ConstIterator, const StackList<T, N>*, const T>(list, i) {}
|
||||
};
|
||||
|
||||
[[nodiscard]] Iterator begin() { return Iterator(this, 0); }
|
||||
[[nodiscard]] Iterator end() { return Iterator(this, this->size); }
|
||||
|
||||
[[nodiscard]] ConstIterator begin() const { return ConstIterator(this, 0); }
|
||||
[[nodiscard]] ConstIterator end() const { return ConstIterator(this, this->size); }
|
||||
|
||||
[[nodiscard]] bool isContiguous() const { return this->vec.empty(); }
|
||||
[[nodiscard]] const T* pArray() const { return this->array.data(); }
|
||||
[[nodiscard]] size_t dataLength() const { return this->size * sizeof(T); }
|
||||
|
||||
const T* populateAlloc(void* alloc) const {
|
||||
auto arraylen = std::min(this->size, N) * sizeof(T);
|
||||
memcpy(alloc, this->array.data(), arraylen);
|
||||
|
||||
if (!this->vec.empty()) {
|
||||
memcpy(
|
||||
static_cast<char*>(alloc) + arraylen, // NOLINT
|
||||
this->vec.data(),
|
||||
this->vec.size() * sizeof(T)
|
||||
);
|
||||
}
|
||||
|
||||
return static_cast<T*>(alloc);
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<T, N> array {};
|
||||
std::vector<T> vec;
|
||||
size_t size = 0;
|
||||
};
|
||||
|
||||
// might be incorrectly aligned depending on type
|
||||
// #define STACKLIST_ALLOCA_VIEW(list) ((list).isContiguous() ? (list).pArray() : (list).populateAlloc(alloca((list).dataLength())))
|
||||
|
||||
// NOLINTBEGIN
|
||||
#define STACKLIST_VLA_VIEW(type, list, var) \
|
||||
const type* var; \
|
||||
type var##Data[(list).length()]; \
|
||||
if ((list).isContiguous()) { \
|
||||
(var) = (list).pArray(); \
|
||||
} else { \
|
||||
(list).populateAlloc(var##Data); \
|
||||
(var) = var##Data; \
|
||||
}
|
||||
// NOLINTEND
|
|
@ -7,3 +7,4 @@ endfunction()
|
|||
qs_test(transformwatcher transformwatcher.cpp)
|
||||
qs_test(ringbuffer ringbuf.cpp)
|
||||
qs_test(scriptmodel scriptmodel.cpp)
|
||||
qs_test(stacklist stacklist.cpp)
|
||||
|
|
|
@ -22,6 +22,7 @@ bool ModelOperation::operator==(const ModelOperation& other) const {
|
|||
&& other.length == this->length && other.destIndex == this->destIndex;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
debug.nospace();
|
||||
|
@ -43,6 +44,7 @@ QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
|
|||
return debug;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
QDebug& operator<<(QDebug& debug, const QVariantList& list) {
|
||||
auto str = QString();
|
||||
|
||||
|
|
92
src/core/test/stacklist.cpp
Normal file
92
src/core/test/stacklist.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#include "stacklist.hpp"
|
||||
#include <cstddef>
|
||||
|
||||
#include <qlist.h>
|
||||
#include <qtest.h>
|
||||
#include <qtestcase.h>
|
||||
|
||||
#include "../stacklist.hpp"
|
||||
|
||||
void TestStackList::push() {
|
||||
StackList<int, 2> list;
|
||||
|
||||
list.push(1);
|
||||
list.push(2);
|
||||
|
||||
QCOMPARE_EQ(list.toList(), QList({1, 2}));
|
||||
QCOMPARE_EQ(list.length(), 2);
|
||||
}
|
||||
|
||||
void TestStackList::pushAndGrow() {
|
||||
StackList<int, 2> list;
|
||||
|
||||
list.push(1);
|
||||
list.push(2);
|
||||
list.push(3);
|
||||
list.push(4);
|
||||
|
||||
QCOMPARE_EQ(list.toList(), QList({1, 2, 3, 4}));
|
||||
QCOMPARE_EQ(list.length(), 4);
|
||||
}
|
||||
|
||||
void TestStackList::copy() {
|
||||
StackList<int, 2> list;
|
||||
|
||||
list.push(1);
|
||||
list.push(2);
|
||||
list.push(3);
|
||||
list.push(4);
|
||||
|
||||
QCOMPARE_EQ(list.toList(), QList({1, 2, 3, 4}));
|
||||
QCOMPARE_EQ(list.length(), 4);
|
||||
|
||||
auto list2 = list;
|
||||
|
||||
QCOMPARE_EQ(list2.toList(), QList({1, 2, 3, 4}));
|
||||
QCOMPARE_EQ(list2.length(), 4);
|
||||
QCOMPARE_EQ(list2, list);
|
||||
}
|
||||
|
||||
void TestStackList::viewVla() {
|
||||
StackList<int, 2> list;
|
||||
|
||||
list.push(1);
|
||||
list.push(2);
|
||||
|
||||
QCOMPARE_EQ(list.toList(), QList({1, 2}));
|
||||
QCOMPARE_EQ(list.length(), 2);
|
||||
|
||||
STACKLIST_VLA_VIEW(int, list, listView);
|
||||
|
||||
QList<int> ql;
|
||||
|
||||
for (size_t i = 0; i != list.length(); ++i) {
|
||||
ql.push_back(listView[i]); // NOLINT
|
||||
}
|
||||
|
||||
QCOMPARE_EQ(ql, list.toList());
|
||||
}
|
||||
|
||||
void TestStackList::viewVlaGrown() {
|
||||
StackList<int, 2> list;
|
||||
|
||||
list.push(1);
|
||||
list.push(2);
|
||||
list.push(3);
|
||||
list.push(4);
|
||||
|
||||
QCOMPARE_EQ(list.toList(), QList({1, 2, 3, 4}));
|
||||
QCOMPARE_EQ(list.length(), 4);
|
||||
|
||||
STACKLIST_VLA_VIEW(int, list, listView);
|
||||
|
||||
QList<int> ql;
|
||||
|
||||
for (size_t i = 0; i != list.length(); ++i) {
|
||||
ql.push_back(listView[i]); // NOLINT
|
||||
}
|
||||
|
||||
QCOMPARE_EQ(ql, list.toList());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestStackList);
|
15
src/core/test/stacklist.hpp
Normal file
15
src/core/test/stacklist.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class TestStackList: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
private slots:
|
||||
static void push();
|
||||
static void pushAndGrow();
|
||||
static void copy();
|
||||
static void viewVla();
|
||||
static void viewVlaGrown();
|
||||
};
|
|
@ -196,7 +196,7 @@ V* AwfulMap<K, V>::get(const K& key) {
|
|||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void AwfulMap<K, V>::insert(K key, V value) {
|
||||
void AwfulMap<K, V>::insert(const K& key, V value) {
|
||||
this->values.push_back(QPair<K, V>(key, value));
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ class AwfulMap {
|
|||
public:
|
||||
[[nodiscard]] bool contains(const K& key) const;
|
||||
[[nodiscard]] V* get(const K& key);
|
||||
void insert(K key, V value); // assumes no duplicates
|
||||
bool remove(const K& key); // returns true if anything was removed
|
||||
void insert(const K& key, V value); // assumes no duplicates
|
||||
bool remove(const K& key); // returns true if anything was removed
|
||||
QList<QPair<K, V>> values;
|
||||
};
|
||||
|
||||
|
|
|
@ -22,7 +22,9 @@ using namespace google_breakpad;
|
|||
|
||||
namespace qs::crash {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
|
||||
}
|
||||
|
||||
struct CrashHandlerPrivate {
|
||||
ExceptionHandler* exceptionHandler = nullptr;
|
||||
|
|
|
@ -22,49 +22,10 @@
|
|||
#include "build.hpp"
|
||||
#include "interface.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
Q_LOGGING_CATEGORY(logCrashReporter, "quickshell.crashreporter", QtWarningMsg);
|
||||
|
||||
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance);
|
||||
|
||||
void qsCheckCrash(int argc, char** argv) {
|
||||
auto fd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD");
|
||||
if (fd.isEmpty()) return;
|
||||
auto app = QApplication(argc, argv);
|
||||
|
||||
RelaunchInfo info;
|
||||
|
||||
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
||||
|
||||
{
|
||||
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
|
||||
|
||||
QFile file;
|
||||
file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
|
||||
file.seek(0);
|
||||
|
||||
auto ds = QDataStream(&file);
|
||||
ds >> info;
|
||||
}
|
||||
|
||||
LogManager::init(
|
||||
!info.noColor,
|
||||
info.timestamp,
|
||||
info.sparseLogsOnly,
|
||||
info.defaultLogLevel,
|
||||
info.logRules
|
||||
);
|
||||
|
||||
auto crashDir = QsPaths::crashDir(info.instance.instanceId);
|
||||
|
||||
qCInfo(logCrashReporter) << "Starting crash reporter...";
|
||||
|
||||
recordCrashInfo(crashDir, info.instance);
|
||||
|
||||
auto gui = CrashReporterGui(crashDir.path(), crashProc);
|
||||
gui.show();
|
||||
exit(QApplication::exec()); // NOLINT
|
||||
}
|
||||
|
||||
int tryDup(int fd, const QString& path) {
|
||||
QFile sourceFile;
|
||||
if (!sourceFile.open(fd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||
|
@ -184,3 +145,44 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
|||
|
||||
qCDebug(logCrashReporter) << "Recorded crash information.";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void qsCheckCrash(int argc, char** argv) {
|
||||
auto fd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD");
|
||||
if (fd.isEmpty()) return;
|
||||
auto app = QApplication(argc, argv);
|
||||
|
||||
RelaunchInfo info;
|
||||
|
||||
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
||||
|
||||
{
|
||||
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
|
||||
|
||||
QFile file;
|
||||
file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
|
||||
file.seek(0);
|
||||
|
||||
auto ds = QDataStream(&file);
|
||||
ds >> info;
|
||||
}
|
||||
|
||||
LogManager::init(
|
||||
!info.noColor,
|
||||
info.timestamp,
|
||||
info.sparseLogsOnly,
|
||||
info.defaultLogLevel,
|
||||
info.logRules
|
||||
);
|
||||
|
||||
auto crashDir = QsPaths::crashDir(info.instance.instanceId);
|
||||
|
||||
qCInfo(logCrashReporter) << "Starting crash reporter...";
|
||||
|
||||
recordCrashInfo(crashDir, info.instance);
|
||||
|
||||
auto gui = CrashReporterGui(crashDir.path(), crashProc);
|
||||
gui.show();
|
||||
exit(QApplication::exec()); // NOLINT
|
||||
}
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
|
||||
namespace qs::dbus {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logDbus, "quickshell.dbus", QtWarningMsg);
|
||||
}
|
||||
|
||||
void tryLaunchService(
|
||||
QObject* parent,
|
||||
|
|
|
@ -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
|
||||
|
@ -368,11 +368,9 @@ void DBusMenu::updateLayoutRecursive(
|
|||
auto childrenChanged = false;
|
||||
auto iter = item->mChildren.begin();
|
||||
while (iter != item->mChildren.end()) {
|
||||
auto existing = std::find_if(
|
||||
layout.children.begin(),
|
||||
layout.children.end(),
|
||||
[&](const DBusMenuLayout& layout) { return layout.id == *iter; }
|
||||
);
|
||||
auto existing = std::ranges::find_if(layout.children, [&](const DBusMenuLayout& layout) {
|
||||
return layout.id == *iter;
|
||||
});
|
||||
|
||||
if (!item->mShowChildren || existing == layout.children.end()) {
|
||||
qCDebug(logDbusMenu) << "Removing missing layout item" << this->items.value(*iter) << "from"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -190,11 +190,9 @@ void DBusPropertyGroup::updateAllViaGetAll() {
|
|||
|
||||
void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool complainMissing) {
|
||||
for (const auto [name, value]: properties.asKeyValueRange()) {
|
||||
auto prop = std::find_if(
|
||||
this->properties.begin(),
|
||||
this->properties.end(),
|
||||
[&name](DBusPropertyCore* prop) { return prop->nameRef() == name; }
|
||||
);
|
||||
auto prop = std::ranges::find_if(this->properties, [&name](DBusPropertyCore* prop) {
|
||||
return prop->nameRef() == name;
|
||||
});
|
||||
|
||||
if (prop == this->properties.end()) {
|
||||
qCDebug(logDbusProperties) << "Ignoring untracked property update" << name << "for"
|
||||
|
@ -312,11 +310,9 @@ void DBusPropertyGroup::onPropertiesChanged(
|
|||
<< "Received property change set and invalidations for" << this->toString();
|
||||
|
||||
for (const auto& name: invalidatedProperties) {
|
||||
auto prop = std::find_if(
|
||||
this->properties.begin(),
|
||||
this->properties.end(),
|
||||
[&name](DBusPropertyCore* prop) { return prop->nameRef() == name; }
|
||||
);
|
||||
auto prop = std::ranges::find_if(this->properties, [&name](DBusPropertyCore* prop) {
|
||||
return prop->nameRef() == name;
|
||||
});
|
||||
|
||||
if (prop == this->properties.end()) {
|
||||
qCDebug(logDbusProperties) << "Ignoring untracked property invalidation" << name << "for"
|
||||
|
|
|
@ -36,8 +36,8 @@ template <typename T>
|
|||
class DBusResult {
|
||||
public:
|
||||
explicit DBusResult() = default;
|
||||
explicit DBusResult(T value): value(std::move(value)) {}
|
||||
explicit DBusResult(QDBusError error): error(std::move(error)) {}
|
||||
DBusResult(T value): value(std::move(value)) {}
|
||||
DBusResult(QDBusError error): error(std::move(error)) {}
|
||||
explicit DBusResult(T value, QDBusError error)
|
||||
: value(std::move(value))
|
||||
, error(std::move(error)) {}
|
||||
|
@ -66,7 +66,7 @@ template <typename T>
|
|||
void asyncReadProperty(
|
||||
QDBusAbstractInterface& interface,
|
||||
const QString& property,
|
||||
std::function<void(T, QDBusError)> callback
|
||||
const std::function<void(T, QDBusError)>& callback
|
||||
) {
|
||||
asyncReadPropertyInternal(
|
||||
QMetaType::fromType<T>(),
|
||||
|
|
|
@ -13,10 +13,12 @@
|
|||
|
||||
namespace qs::debug {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logLint, "quickshell.linter", QtWarningMsg);
|
||||
|
||||
void lintZeroSized(QQuickItem* item);
|
||||
bool isRenderable(QQuickItem* item);
|
||||
} // namespace
|
||||
|
||||
void lintObjectTree(QObject* object) {
|
||||
if (!logLint().isWarningEnabled()) return;
|
||||
|
@ -41,6 +43,8 @@ void lintItemTree(QQuickItem* item) {
|
|||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void lintZeroSized(QQuickItem* item) {
|
||||
if (!item->isEnabled() || !item->isVisible()) return;
|
||||
if (item->childItems().isEmpty()) return;
|
||||
|
@ -71,4 +75,6 @@ bool isRenderable(QQuickItem* item) {
|
|||
return std::ranges::any_of(item->childItems(), [](auto* item) { return isRenderable(item); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace qs::debug
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
|
||||
namespace qs::io {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logFileView, "quickshell.io.fileview", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString FileViewError::toString(FileViewError::Enum value) {
|
||||
switch (value) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,16 +34,310 @@ namespace qs::launch {
|
|||
|
||||
using qs::ipc::IpcClient;
|
||||
|
||||
int readLogFile(CommandState& cmd);
|
||||
int listInstances(CommandState& cmd);
|
||||
int killInstances(CommandState& cmd);
|
||||
int msgInstance(CommandState& cmd);
|
||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication);
|
||||
int locateConfigFile(CommandState& cmd, QString& path);
|
||||
namespace {
|
||||
|
||||
int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
if (!cmd.config.path->isEmpty()) {
|
||||
path = *cmd.config.path;
|
||||
} else {
|
||||
auto manifestPath = *cmd.config.manifest;
|
||||
if (manifestPath.isEmpty()) {
|
||||
auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
||||
auto path = configDir.filePath("manifest.conf");
|
||||
if (QFileInfo(path).isFile()) manifestPath = path;
|
||||
}
|
||||
|
||||
if (!manifestPath.isEmpty()) {
|
||||
auto file = QFile(manifestPath);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
auto stream = QTextStream(&file);
|
||||
while (!stream.atEnd()) {
|
||||
auto line = stream.readLine();
|
||||
if (line.trimmed().startsWith("#")) continue;
|
||||
if (line.trimmed().isEmpty()) continue;
|
||||
|
||||
auto split = line.split('=');
|
||||
if (split.length() != 2) {
|
||||
qCritical() << "Manifest line not in expected format 'name = relativepath':" << line;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (split[0].trimmed() == *cmd.config.name) {
|
||||
path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isEmpty()) {
|
||||
qCCritical(logBare) << "Configuration" << *cmd.config.name
|
||||
<< "not found when searching manifest" << manifestPath;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
||||
|
||||
if (cmd.config.name->isEmpty()) {
|
||||
path = configDir.path();
|
||||
} else {
|
||||
path = configDir.filePath(*cmd.config.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (QFileInfo(path).isDir()) {
|
||||
path = QDir(path).filePath("shell.qml");
|
||||
}
|
||||
|
||||
if (!QFileInfo(path).isFile()) {
|
||||
qCCritical(logBare) << "Could not open config file at" << path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
path = QFileInfo(path).canonicalFilePath();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
int selectInstance(CommandState& cmd, InstanceLockInfo* instance) {
|
||||
auto* basePath = QsPaths::instance()->baseRunDir();
|
||||
if (!basePath) return -1;
|
||||
|
||||
QString path;
|
||||
|
||||
if (cmd.instance.pid != -1) {
|
||||
path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid));
|
||||
if (!QsPaths::checkLock(path, instance)) {
|
||||
qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
|
||||
return -1;
|
||||
}
|
||||
} else if (!cmd.instance.id->isEmpty()) {
|
||||
path = basePath->filePath("by-pid");
|
||||
auto instances = QsPaths::collectInstances(path);
|
||||
|
||||
instances.removeIf([&](const InstanceLockInfo& info) {
|
||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||
});
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
|
||||
return -1;
|
||||
} else if (instances.length() != 1) {
|
||||
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
|
||||
|
||||
for (auto& instance: instances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
}
|
||||
|
||||
return -1;
|
||||
} else {
|
||||
*instance = instances.value(0);
|
||||
}
|
||||
} else {
|
||||
QString configFilePath;
|
||||
auto r = locateConfigFile(cmd, configFilePath);
|
||||
if (r != 0) return r;
|
||||
|
||||
auto pathId =
|
||||
QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
|
||||
auto instances = QsPaths::collectInstances(path);
|
||||
sortInstances(instances, cmd.config.newest);
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*instance = instances.value(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readLogFile(CommandState& cmd) {
|
||||
auto path = *cmd.log.file;
|
||||
|
||||
if (path.isEmpty()) {
|
||||
InstanceLockInfo instance;
|
||||
auto r = selectInstance(cmd, &instance);
|
||||
if (r != 0) return r;
|
||||
|
||||
path = QDir(QsPaths::basePath(instance.instance.instanceId)).filePath("log.qslog");
|
||||
}
|
||||
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCCritical(logBare) << "Failed to open log file" << path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return qs::log::readEncodedLogs(
|
||||
&file,
|
||||
path,
|
||||
cmd.log.timestamp,
|
||||
cmd.log.tail,
|
||||
cmd.log.follow,
|
||||
*cmd.log.readoutRules
|
||||
)
|
||||
? 0
|
||||
: -1;
|
||||
}
|
||||
|
||||
int listInstances(CommandState& cmd) {
|
||||
auto* basePath = QsPaths::instance()->baseRunDir();
|
||||
if (!basePath) return -1; // NOLINT
|
||||
|
||||
QString path;
|
||||
QString configFilePath;
|
||||
if (cmd.instance.all) {
|
||||
path = basePath->filePath("by-pid");
|
||||
} else {
|
||||
auto r = locateConfigFile(cmd, configFilePath);
|
||||
|
||||
if (r != 0) {
|
||||
qCInfo(logBare) << "Use --all to list all instances.";
|
||||
return r;
|
||||
}
|
||||
|
||||
auto pathId =
|
||||
QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
}
|
||||
|
||||
auto instances = QsPaths::collectInstances(path);
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
if (cmd.instance.all) {
|
||||
qCInfo(logBare) << "No running instances.";
|
||||
} else {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
qCInfo(logBare) << "Use --all to list all instances.";
|
||||
}
|
||||
} else {
|
||||
sortInstances(instances, cmd.config.newest);
|
||||
|
||||
if (cmd.output.json) {
|
||||
auto array = QJsonArray();
|
||||
|
||||
for (auto& instance: instances) {
|
||||
auto json = QJsonObject();
|
||||
|
||||
json["id"] = instance.instance.instanceId;
|
||||
json["pid"] = instance.pid;
|
||||
json["shell_id"] = instance.instance.shellId;
|
||||
json["config_path"] = instance.instance.configPath;
|
||||
json["launch_time"] = instance.instance.launchTime.toString(Qt::ISODate);
|
||||
|
||||
array.push_back(json);
|
||||
}
|
||||
|
||||
auto document = QJsonDocument(array);
|
||||
QTextStream(stdout) << document.toJson(QJsonDocument::Indented);
|
||||
} else {
|
||||
for (auto& instance: instances) {
|
||||
auto launchTimeStr = instance.instance.launchTime.toString("yyyy-MM-dd hh:mm:ss");
|
||||
|
||||
auto runSeconds = instance.instance.launchTime.secsTo(QDateTime::currentDateTime());
|
||||
auto remSeconds = runSeconds % 60;
|
||||
auto runMinutes = (runSeconds - remSeconds) / 60;
|
||||
auto remMinutes = runMinutes % 60;
|
||||
auto runHours = (runMinutes - remMinutes) / 60;
|
||||
auto runtimeStr = QString("%1 hours, %2 minutes, %3 seconds")
|
||||
.arg(runHours)
|
||||
.arg(remMinutes)
|
||||
.arg(remSeconds);
|
||||
|
||||
qCInfo(logBare).noquote().nospace()
|
||||
<< "Instance " << instance.instance.instanceId << ":\n"
|
||||
<< " Process ID: " << instance.pid << '\n'
|
||||
<< " Shell ID: " << instance.instance.shellId << '\n'
|
||||
<< " Config path: " << instance.instance.configPath << '\n'
|
||||
<< " Launch time: " << launchTimeStr << " (running for " << runtimeStr << ")\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int killInstances(CommandState& cmd) {
|
||||
InstanceLockInfo instance;
|
||||
auto r = selectInstance(cmd, &instance);
|
||||
if (r != 0) return r;
|
||||
|
||||
return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) {
|
||||
client.kill();
|
||||
qCInfo(logBare).noquote() << "Killed" << instance.instance.instanceId;
|
||||
});
|
||||
}
|
||||
|
||||
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.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.name, arguments);
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
|
||||
QString configPath;
|
||||
|
||||
auto r = locateConfigFile(cmd, configPath);
|
||||
if (r != 0) return r;
|
||||
|
||||
{
|
||||
InstanceLockInfo info;
|
||||
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
|
||||
qCInfo(logBare) << "An instance of this configuration is already running.";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return launch(
|
||||
{
|
||||
.configPath = configPath,
|
||||
.debugPort = cmd.debug.port,
|
||||
.waitForDebug = cmd.debug.wait,
|
||||
},
|
||||
cmd.exec.argv,
|
||||
coreApplication
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||
auto state = CommandState();
|
||||
if (auto ret = parseCommand(argc, argv, state); ret != 0) return ret;
|
||||
if (auto ret = parseCommand(argc, argv, state); ret != 65535) return ret;
|
||||
|
||||
if (state.misc.checkCompat) {
|
||||
if (strcmp(qVersion(), QT_VERSION_STR) != 0) {
|
||||
|
@ -126,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
|
||||
|
@ -142,306 +436,4 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
if (!cmd.config.path->isEmpty()) {
|
||||
path = *cmd.config.path;
|
||||
} else {
|
||||
auto manifestPath = *cmd.config.manifest;
|
||||
if (manifestPath.isEmpty()) {
|
||||
auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
||||
auto path = configDir.filePath("manifest.conf");
|
||||
if (QFileInfo(path).isFile()) manifestPath = path;
|
||||
}
|
||||
|
||||
if (!manifestPath.isEmpty()) {
|
||||
auto file = QFile(manifestPath);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
auto stream = QTextStream(&file);
|
||||
while (!stream.atEnd()) {
|
||||
auto line = stream.readLine();
|
||||
if (line.trimmed().startsWith("#")) continue;
|
||||
if (line.trimmed().isEmpty()) continue;
|
||||
|
||||
auto split = line.split('=');
|
||||
if (split.length() != 2) {
|
||||
qCritical() << "Manifest line not in expected format 'name = relativepath':" << line;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (split[0].trimmed() == *cmd.config.name) {
|
||||
path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isEmpty()) {
|
||||
qCCritical(logBare) << "Configuration" << *cmd.config.name
|
||||
<< "not found when searching manifest" << manifestPath;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
||||
|
||||
if (cmd.config.name->isEmpty()) {
|
||||
path = configDir.path();
|
||||
} else {
|
||||
path = configDir.filePath(*cmd.config.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (QFileInfo(path).isDir()) {
|
||||
path = QDir(path).filePath("shell.qml");
|
||||
}
|
||||
|
||||
if (!QFileInfo(path).isFile()) {
|
||||
qCCritical(logBare) << "Could not open config file at" << path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
path = QFileInfo(path).canonicalFilePath();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sortInstances(QVector<InstanceLockInfo>& list) {
|
||||
std::sort(list.begin(), list.end(), [](const InstanceLockInfo& a, const InstanceLockInfo& b) {
|
||||
return a.instance.launchTime < b.instance.launchTime;
|
||||
});
|
||||
};
|
||||
|
||||
int selectInstance(CommandState& cmd, InstanceLockInfo* instance) {
|
||||
auto* basePath = QsPaths::instance()->baseRunDir();
|
||||
if (!basePath) return -1;
|
||||
|
||||
QString path;
|
||||
|
||||
if (cmd.instance.pid != -1) {
|
||||
path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid));
|
||||
if (!QsPaths::checkLock(path, instance)) {
|
||||
qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
|
||||
return -1;
|
||||
}
|
||||
} else if (!cmd.instance.id->isEmpty()) {
|
||||
path = basePath->filePath("by-pid");
|
||||
auto instances = QsPaths::collectInstances(path);
|
||||
|
||||
auto itr =
|
||||
std::remove_if(instances.begin(), instances.end(), [&](const InstanceLockInfo& info) {
|
||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||
});
|
||||
|
||||
instances.erase(itr, instances.end());
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
|
||||
return -1;
|
||||
} else if (instances.length() != 1) {
|
||||
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
|
||||
|
||||
for (auto& instance: instances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
}
|
||||
|
||||
return -1;
|
||||
} else {
|
||||
*instance = instances.value(0);
|
||||
}
|
||||
} else {
|
||||
QString configFilePath;
|
||||
auto r = locateConfigFile(cmd, configFilePath);
|
||||
if (r != 0) return r;
|
||||
|
||||
auto pathId =
|
||||
QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
|
||||
auto instances = QsPaths::collectInstances(path);
|
||||
sortInstances(instances);
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*instance = instances.value(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readLogFile(CommandState& cmd) {
|
||||
auto path = *cmd.log.file;
|
||||
|
||||
if (path.isEmpty()) {
|
||||
InstanceLockInfo instance;
|
||||
auto r = selectInstance(cmd, &instance);
|
||||
if (r != 0) return r;
|
||||
|
||||
path = QDir(QsPaths::basePath(instance.instance.instanceId)).filePath("log.qslog");
|
||||
}
|
||||
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCCritical(logBare) << "Failed to open log file" << path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return qs::log::readEncodedLogs(
|
||||
&file,
|
||||
path,
|
||||
cmd.log.timestamp,
|
||||
cmd.log.tail,
|
||||
cmd.log.follow,
|
||||
*cmd.log.readoutRules
|
||||
)
|
||||
? 0
|
||||
: -1;
|
||||
}
|
||||
|
||||
int listInstances(CommandState& cmd) {
|
||||
auto* basePath = QsPaths::instance()->baseRunDir();
|
||||
if (!basePath) return -1; // NOLINT
|
||||
|
||||
QString path;
|
||||
QString configFilePath;
|
||||
if (cmd.instance.all) {
|
||||
path = basePath->filePath("by-pid");
|
||||
} else {
|
||||
auto r = locateConfigFile(cmd, configFilePath);
|
||||
|
||||
if (r != 0) {
|
||||
qCInfo(logBare) << "Use --all to list all instances.";
|
||||
return r;
|
||||
}
|
||||
|
||||
auto pathId =
|
||||
QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
}
|
||||
|
||||
auto instances = QsPaths::collectInstances(path);
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
if (cmd.instance.all) {
|
||||
qCInfo(logBare) << "No running instances.";
|
||||
} else {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
qCInfo(logBare) << "Use --all to list all instances.";
|
||||
}
|
||||
} else {
|
||||
sortInstances(instances);
|
||||
|
||||
if (cmd.output.json) {
|
||||
auto array = QJsonArray();
|
||||
|
||||
for (auto& instance: instances) {
|
||||
auto json = QJsonObject();
|
||||
|
||||
json["id"] = instance.instance.instanceId;
|
||||
json["pid"] = instance.pid;
|
||||
json["shell_id"] = instance.instance.shellId;
|
||||
json["config_path"] = instance.instance.configPath;
|
||||
json["launch_time"] = instance.instance.launchTime.toString(Qt::ISODate);
|
||||
|
||||
array.push_back(json);
|
||||
}
|
||||
|
||||
auto document = QJsonDocument(array);
|
||||
QTextStream(stdout) << document.toJson(QJsonDocument::Indented);
|
||||
} else {
|
||||
for (auto& instance: instances) {
|
||||
auto launchTimeStr = instance.instance.launchTime.toString("yyyy-MM-dd hh:mm:ss");
|
||||
|
||||
auto runSeconds = instance.instance.launchTime.secsTo(QDateTime::currentDateTime());
|
||||
auto remSeconds = runSeconds % 60;
|
||||
auto runMinutes = (runSeconds - remSeconds) / 60;
|
||||
auto remMinutes = runMinutes % 60;
|
||||
auto runHours = (runMinutes - remMinutes) / 60;
|
||||
auto runtimeStr = QString("%1 hours, %2 minutes, %3 seconds")
|
||||
.arg(runHours)
|
||||
.arg(remMinutes)
|
||||
.arg(remSeconds);
|
||||
|
||||
qCInfo(logBare).noquote().nospace()
|
||||
<< "Instance " << instance.instance.instanceId << ":\n"
|
||||
<< " Process ID: " << instance.pid << '\n'
|
||||
<< " Shell ID: " << instance.instance.shellId << '\n'
|
||||
<< " Config path: " << instance.instance.configPath << '\n'
|
||||
<< " Launch time: " << launchTimeStr << " (running for " << runtimeStr << ")\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int killInstances(CommandState& cmd) {
|
||||
InstanceLockInfo instance;
|
||||
auto r = selectInstance(cmd, &instance);
|
||||
if (r != 0) return r;
|
||||
|
||||
return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) {
|
||||
client.kill();
|
||||
qCInfo(logBare).noquote() << "Killed" << instance.instance.instanceId;
|
||||
});
|
||||
}
|
||||
|
||||
int msgInstance(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);
|
||||
} 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 -1;
|
||||
});
|
||||
}
|
||||
|
||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
|
||||
QString configPath;
|
||||
|
||||
auto r = locateConfigFile(cmd, configPath);
|
||||
if (r != 0) return r;
|
||||
|
||||
{
|
||||
InstanceLockInfo info;
|
||||
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
|
||||
qCInfo(logBare) << "An instance of this configuration is already running.";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return launch(
|
||||
{
|
||||
.configPath = configPath,
|
||||
.debugPort = cmd.debug.port,
|
||||
.waitForDebug = cmd.debug.wait,
|
||||
},
|
||||
cmd.exec.argv,
|
||||
coreApplication
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace qs::launch
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
namespace qs::launch {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
QString base36Encode(T number) {
|
||||
const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
@ -52,6 +54,8 @@ QString base36Encode(T number) {
|
|||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication) {
|
||||
auto pathId = QCryptographicHash::hash(args.configPath.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||
auto shellId = QString(pathId);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -22,36 +22,7 @@
|
|||
|
||||
namespace qs::launch {
|
||||
|
||||
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication);
|
||||
|
||||
int DAEMON_PIPE = -1; // NOLINT
|
||||
|
||||
void exitDaemon(int code) {
|
||||
if (DAEMON_PIPE == -1) return;
|
||||
|
||||
if (write(DAEMON_PIPE, &code, sizeof(int)) == -1) {
|
||||
qCritical().nospace() << "Failed to write daemon exit command with error code " << errno << ": "
|
||||
<< qt_error_string();
|
||||
}
|
||||
|
||||
close(DAEMON_PIPE);
|
||||
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdin";
|
||||
}
|
||||
|
||||
if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdout";
|
||||
}
|
||||
|
||||
if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stderr";
|
||||
}
|
||||
}
|
||||
namespace {
|
||||
|
||||
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
||||
#if CRASH_REPORTER
|
||||
|
@ -96,6 +67,37 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
|||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int DAEMON_PIPE = -1; // NOLINT
|
||||
|
||||
void exitDaemon(int code) {
|
||||
if (DAEMON_PIPE == -1) return;
|
||||
|
||||
if (write(DAEMON_PIPE, &code, sizeof(int)) == -1) {
|
||||
qCritical().nospace() << "Failed to write daemon exit command with error code " << errno << ": "
|
||||
<< qt_error_string();
|
||||
}
|
||||
|
||||
close(DAEMON_PIPE);
|
||||
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdin";
|
||||
}
|
||||
|
||||
if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdout";
|
||||
}
|
||||
|
||||
if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stderr";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
QCoreApplication::setApplicationName("quickshell");
|
||||
|
||||
|
|
|
@ -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,43 +161,78 @@ 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;
|
||||
}
|
||||
|
||||
CLI11_PARSE(*cli, argc, argv);
|
||||
|
||||
return 0;
|
||||
return 65535;
|
||||
}
|
||||
|
||||
} // namespace qs::launch
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
#include "../../core/generation.hpp"
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logGreetd, "quickshell.service.greetd");
|
||||
}
|
||||
|
||||
QString GreetdState::toString(GreetdState::Enum value) {
|
||||
switch (value) {
|
||||
|
|
|
@ -21,7 +21,9 @@ using namespace qs::dbus;
|
|||
|
||||
namespace qs::service::mpris {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logMprisPlayer, "quickshell.service.mp.player", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString MprisPlaybackState::toString(MprisPlaybackState::Enum status) {
|
||||
switch (status) {
|
||||
|
@ -237,7 +239,7 @@ void MprisPlayer::setPosition(qreal position) {
|
|||
this->player->Seek(target - pos);
|
||||
}
|
||||
|
||||
this->bpPosition = target;
|
||||
this->setPosition(target);
|
||||
}
|
||||
|
||||
void MprisPlayer::onPositionUpdated() {
|
||||
|
@ -248,11 +250,16 @@ void MprisPlayer::onPositionUpdated() {
|
|||
if (firstChange) emit this->positionSupportedChanged();
|
||||
}
|
||||
|
||||
void MprisPlayer::setPosition(qlonglong position) {
|
||||
this->bpPosition = position;
|
||||
this->onPositionUpdated();
|
||||
}
|
||||
|
||||
void MprisPlayer::onExportedPositionChanged() {
|
||||
if (!this->lengthSupported()) emit this->lengthChanged();
|
||||
}
|
||||
|
||||
void MprisPlayer::onSeek(qlonglong time) { this->bpPosition = time; }
|
||||
void MprisPlayer::onSeek(qlonglong time) { this->setPosition(time); }
|
||||
|
||||
qreal MprisPlayer::length() const {
|
||||
if (this->bInternalLength == -1) {
|
||||
|
|
|
@ -391,6 +391,8 @@ private slots:
|
|||
private:
|
||||
void onMetadataChanged();
|
||||
void onPositionUpdated();
|
||||
// call instead of setting bpPosition
|
||||
void setPosition(qlonglong position);
|
||||
void requestPositionUpdate() { this->pPosition.requestUpdate(); };
|
||||
|
||||
// clang-format off
|
||||
|
@ -457,7 +459,7 @@ private:
|
|||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanSeek, bpCanSeek, playerProperties, "CanSeek");
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanGoNext, bpCanGoNext, playerProperties, "CanGoNext");
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanGoPrevious, bpCanGoPrevious, playerProperties, "CanGoPrevious");
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, qlonglong, pPosition, bpPosition, playerProperties, "Position", false);
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, qlonglong, pPosition, bpPosition, onPositionUpdated, playerProperties, "Position", false);
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pVolume, bVolume, playerProperties, "Volume", false);
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMetadata, bpMetadata, playerProperties, "Metadata");
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pPlaybackStatus, bpPlaybackStatus, playerProperties, "PlaybackStatus");
|
||||
|
@ -466,8 +468,6 @@ private:
|
|||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMinRate, bMinRate, playerProperties, "MinimumRate", false);
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMaxRate, bMaxRate, playerProperties, "MaximumRate", false);
|
||||
QS_DBUS_PROPERTY_BINDING(MprisPlayer, pShuffle, bShuffle, playerProperties, "Shuffle", false);
|
||||
|
||||
QS_BINDING_SUBSCRIBE_METHOD(MprisPlayer, bpPosition, onPositionUpdated, onValueChanged);
|
||||
// clang-format on
|
||||
|
||||
QDateTime lastPositionTimestamp;
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
|
||||
namespace qs::service::mpris {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logMprisWatcher, "quickshell.service.mpris.watcher", QtWarningMsg);
|
||||
}
|
||||
|
||||
MprisWatcher::MprisWatcher() {
|
||||
qCDebug(logMprisWatcher) << "Starting MprisWatcher";
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
namespace qs::service::notifications {
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logNotifications); // server.cpp
|
||||
|
||||
QImage DBusNotificationImage::createImage() const {
|
||||
|
|
|
@ -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>
|
||||
|
@ -18,6 +17,7 @@
|
|||
|
||||
namespace qs::service::notifications {
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logNotifications); // server.cpp
|
||||
|
||||
QString NotificationUrgency::toString(NotificationUrgency::Enum value) {
|
||||
|
@ -116,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
|
||||
|
@ -130,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
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
namespace qs::service::notifications {
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
Q_LOGGING_CATEGORY(logNotifications, "quickshell.service.notifications");
|
||||
|
||||
NotificationServer::NotificationServer() {
|
||||
|
|
|
@ -10,12 +10,28 @@
|
|||
#include <qobject.h>
|
||||
#include <qsocketnotifier.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <spa/utils/defs.h>
|
||||
#include <spa/utils/hook.h>
|
||||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logLoop, "quickshell.service.pipewire.loop", QtWarningMsg);
|
||||
}
|
||||
|
||||
const pw_core_events PwCore::EVENTS = {
|
||||
.version = PW_VERSION_CORE_EVENTS,
|
||||
.info = nullptr,
|
||||
.done = &PwCore::onSync,
|
||||
.ping = nullptr,
|
||||
.error = nullptr,
|
||||
.remove_id = nullptr,
|
||||
.bound_id = nullptr,
|
||||
.add_mem = nullptr,
|
||||
.remove_mem = nullptr,
|
||||
.bound_props = nullptr,
|
||||
};
|
||||
|
||||
PwCore::PwCore(QObject* parent): QObject(parent), notifier(QSocketNotifier::Read) {
|
||||
qCInfo(logLoop) << "Creating pipewire event loop.";
|
||||
|
@ -40,6 +56,8 @@ PwCore::PwCore(QObject* parent): QObject(parent), notifier(QSocketNotifier::Read
|
|||
return;
|
||||
}
|
||||
|
||||
pw_core_add_listener(this->core, &this->listener.hook, &PwCore::EVENTS, this);
|
||||
|
||||
qCInfo(logLoop) << "Linking pipewire event loop.";
|
||||
// Tie the pw event loop into qt.
|
||||
auto fd = pw_loop_get_fd(this->loop);
|
||||
|
@ -77,6 +95,16 @@ void PwCore::poll() {
|
|||
emit this->polled();
|
||||
}
|
||||
|
||||
qint32 PwCore::sync(quint32 id) const {
|
||||
// Seq param doesn't seem to do anything. Seq is instead the returned value.
|
||||
return pw_core_sync(this->core, id, 0);
|
||||
}
|
||||
|
||||
void PwCore::onSync(void* data, quint32 id, qint32 seq) {
|
||||
auto* self = static_cast<PwCore*>(data);
|
||||
emit self->synced(id, seq);
|
||||
}
|
||||
|
||||
SpaHook::SpaHook() { // NOLINT
|
||||
spa_zero(this->hook);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@
|
|||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
class SpaHook {
|
||||
public:
|
||||
explicit SpaHook();
|
||||
|
||||
void remove();
|
||||
spa_hook hook;
|
||||
};
|
||||
|
||||
class PwCore: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
|
@ -23,6 +31,7 @@ public:
|
|||
Q_DISABLE_COPY_MOVE(PwCore);
|
||||
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] qint32 sync(quint32 id) const;
|
||||
|
||||
pw_loop* loop = nullptr;
|
||||
pw_context* context = nullptr;
|
||||
|
@ -30,12 +39,18 @@ public:
|
|||
|
||||
signals:
|
||||
void polled();
|
||||
void synced(quint32 id, qint32 seq);
|
||||
|
||||
private slots:
|
||||
void poll();
|
||||
|
||||
private:
|
||||
static const pw_core_events EVENTS;
|
||||
|
||||
static void onSync(void* data, quint32 id, qint32 seq);
|
||||
|
||||
QSocketNotifier notifier;
|
||||
SpaHook listener;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
@ -49,12 +64,4 @@ public:
|
|||
T* object;
|
||||
};
|
||||
|
||||
class SpaHook {
|
||||
public:
|
||||
explicit SpaHook();
|
||||
|
||||
void remove();
|
||||
spa_hook hook;
|
||||
};
|
||||
|
||||
} // namespace qs::service::pipewire
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logDefaults, "quickshell.service.pipewire.defaults", QtWarningMsg);
|
||||
}
|
||||
|
||||
PwDefaultTracker::PwDefaultTracker(PwRegistry* registry): registry(registry) {
|
||||
QObject::connect(registry, &PwRegistry::metadataAdded, this, &PwDefaultTracker::onMetadataAdded);
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logDevice, "quickshell.service.pipewire.device", QtWarningMsg);
|
||||
}
|
||||
|
||||
// https://github.com/PipeWire/wireplumber/blob/895c1c7286e8809fad869059179e53ab39c807e9/modules/module-mixer-api.c#L397
|
||||
// https://github.com/PipeWire/pipewire/blob/48c2e9516585ccc791335bc7baf4af6952ec54a0/src/modules/module-protocol-pulse/pulse-server.c#L2743-L2743
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logLink, "quickshell.service.pipewire.link", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString PwLinkState::toString(Enum value) {
|
||||
return QString(pw_link_state_as_string(static_cast<pw_link_state>(value)));
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logMeta, "quickshell.service.pipewire.metadata", QtWarningMsg);
|
||||
}
|
||||
|
||||
void PwMetadata::bindHooks() {
|
||||
pw_metadata_add_listener(this->proxy(), &this->listener.hook, &PwMetadata::EVENTS, this);
|
||||
|
|
|
@ -24,11 +24,15 @@
|
|||
#include <spa/utils/keys.h>
|
||||
#include <spa/utils/type.h>
|
||||
|
||||
#include "connection.hpp"
|
||||
#include "core.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace qs::service::pipewire {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logNode, "quickshell.service.pipewire.node", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString PwAudioChannel::toString(Enum value) {
|
||||
switch (value) {
|
||||
|
@ -90,6 +94,12 @@ void PwNode::bindHooks() {
|
|||
}
|
||||
|
||||
void PwNode::unbindHooks() {
|
||||
if (this->ready) {
|
||||
this->ready = false;
|
||||
emit this->readyChanged();
|
||||
}
|
||||
|
||||
this->syncSeq = 0;
|
||||
this->listener.remove();
|
||||
this->routeDevice = -1;
|
||||
this->properties.clear();
|
||||
|
@ -199,6 +209,20 @@ void PwNode::onInfo(void* data, const pw_node_info* info) {
|
|||
if (self->boundData != nullptr) {
|
||||
self->boundData->onInfo(info);
|
||||
}
|
||||
|
||||
if (!self->ready && !self->syncSeq) {
|
||||
auto* core = PwConnection::instance()->registry.core;
|
||||
QObject::connect(core, &PwCore::synced, self, &PwNode::onCoreSync);
|
||||
self->syncSeq = core->sync(self->id);
|
||||
}
|
||||
}
|
||||
|
||||
void PwNode::onCoreSync(quint32 id, qint32 seq) {
|
||||
if (id != this->id || seq != this->syncSeq) return;
|
||||
qCInfo(logNode) << "Completed initial sync for" << this;
|
||||
this->ready = true;
|
||||
this->syncSeq = 0;
|
||||
emit this->readyChanged();
|
||||
}
|
||||
|
||||
void PwNode::onParam(
|
||||
|
|
|
@ -172,6 +172,7 @@ public:
|
|||
PwNodeType type = PwNodeType::Untracked;
|
||||
bool isSink = false;
|
||||
bool isStream = false;
|
||||
bool ready = false;
|
||||
|
||||
PwNodeBoundData* boundData = nullptr;
|
||||
|
||||
|
@ -180,6 +181,10 @@ public:
|
|||
|
||||
signals:
|
||||
void propertiesChanged();
|
||||
void readyChanged();
|
||||
|
||||
private slots:
|
||||
void onCoreSync(quint32 id, qint32 seq);
|
||||
|
||||
private:
|
||||
static const pw_node_events EVENTS;
|
||||
|
@ -187,6 +192,7 @@ private:
|
|||
static void
|
||||
onParam(void* data, qint32 seq, quint32 id, quint32 index, quint32 next, const spa_pod* param);
|
||||
|
||||
qint32 syncSeq = 0;
|
||||
SpaHook listener;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlist.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
@ -87,6 +88,16 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) {
|
|||
this,
|
||||
&Pipewire::defaultConfiguredAudioSourceChanged
|
||||
);
|
||||
|
||||
if (!connection->registry.isInitialized()) {
|
||||
QObject::connect(
|
||||
&connection->registry,
|
||||
&PwRegistry::initialized,
|
||||
this,
|
||||
&Pipewire::readyChanged,
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ObjectModel<PwNodeIface>* Pipewire::nodes() { return &this->mNodes; }
|
||||
|
@ -156,6 +167,8 @@ void Pipewire::setDefaultConfiguredAudioSource(PwNodeIface* node) {
|
|||
PwConnection::instance()->defaults.changeConfiguredSource(node ? node->node() : nullptr);
|
||||
}
|
||||
|
||||
bool Pipewire::isReady() { return PwConnection::instance()->registry.isInitialized(); }
|
||||
|
||||
PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; }
|
||||
|
||||
void PwNodeLinkTracker::setNode(PwNodeIface* node) {
|
||||
|
@ -298,6 +311,7 @@ void PwNodeAudioIface::setVolumes(const QVector<float>& volumes) {
|
|||
|
||||
PwNodeIface::PwNodeIface(PwNode* node): PwObjectIface(node), mNode(node) {
|
||||
QObject::connect(node, &PwNode::propertiesChanged, this, &PwNodeIface::propertiesChanged);
|
||||
QObject::connect(node, &PwNode::readyChanged, this, &PwNodeIface::readyChanged);
|
||||
|
||||
if (auto* audioBoundData = dynamic_cast<PwNodeBoundAudio*>(node->boundData)) {
|
||||
this->audioIface = new PwNodeAudioIface(audioBoundData, this);
|
||||
|
@ -318,6 +332,8 @@ bool PwNodeIface::isSink() const { return this->mNode->isSink; }
|
|||
|
||||
bool PwNodeIface::isStream() const { return this->mNode->isStream; }
|
||||
|
||||
bool PwNodeIface::isReady() const { return this->mNode->ready; }
|
||||
|
||||
QVariantMap PwNodeIface::properties() const {
|
||||
auto map = QVariantMap();
|
||||
for (auto [k, v]: this->mNode->properties.asKeyValueRange()) {
|
||||
|
|
|
@ -116,6 +116,13 @@ class Pipewire: public QObject {
|
|||
///
|
||||
/// See @@defaultAudioSource for the current default source, regardless of preference.
|
||||
Q_PROPERTY(qs::service::pipewire::PwNodeIface* preferredDefaultAudioSource READ defaultConfiguredAudioSource WRITE setDefaultConfiguredAudioSource NOTIFY defaultConfiguredAudioSourceChanged);
|
||||
/// This property is true if quickshell has completed its initial sync with
|
||||
/// the pipewire server. If true, nodes, links and sync/source preferences will be
|
||||
/// in a good state.
|
||||
///
|
||||
/// > [!NOTE] You can use the pipewire object before it is ready, but some nodes/links
|
||||
/// > may be missing, and preference metadata may be null.
|
||||
Q_PROPERTY(bool ready READ isReady NOTIFY readyChanged);
|
||||
// clang-format on
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
@ -136,6 +143,8 @@ public:
|
|||
[[nodiscard]] PwNodeIface* defaultConfiguredAudioSource() const;
|
||||
static void setDefaultConfiguredAudioSource(PwNodeIface* node);
|
||||
|
||||
[[nodiscard]] static bool isReady();
|
||||
|
||||
signals:
|
||||
void defaultAudioSinkChanged();
|
||||
void defaultAudioSourceChanged();
|
||||
|
@ -143,6 +152,8 @@ signals:
|
|||
void defaultConfiguredAudioSinkChanged();
|
||||
void defaultConfiguredAudioSourceChanged();
|
||||
|
||||
void readyChanged();
|
||||
|
||||
private slots:
|
||||
void onNodeAdded(PwNode* node);
|
||||
void onNodeRemoved(QObject* object);
|
||||
|
@ -294,6 +305,11 @@ class PwNodeIface: public PwObjectIface {
|
|||
/// The presence or absence of this property can be used to determine if a node
|
||||
/// manages audio, regardless of if it is bound. If non null, the node is an audio node.
|
||||
Q_PROPERTY(qs::service::pipewire::PwNodeAudioIface* audio READ audio CONSTANT);
|
||||
/// True if the node is fully bound and ready to use.
|
||||
///
|
||||
/// > [!NOTE] The node may be used before it is fully bound, but some data
|
||||
/// > may be missing or incorrect.
|
||||
Q_PROPERTY(bool ready READ isReady NOTIFY readyChanged);
|
||||
QML_NAMED_ELEMENT(PwNode);
|
||||
QML_UNCREATABLE("PwNodes cannot be created directly");
|
||||
|
||||
|
@ -307,6 +323,7 @@ public:
|
|||
[[nodiscard]] QString nickname() const;
|
||||
[[nodiscard]] bool isSink() const;
|
||||
[[nodiscard]] bool isStream() const;
|
||||
[[nodiscard]] bool isReady() const;
|
||||
[[nodiscard]] QVariantMap properties() const;
|
||||
[[nodiscard]] PwNodeAudioIface* audio() const;
|
||||
|
||||
|
@ -314,6 +331,7 @@ public:
|
|||
|
||||
signals:
|
||||
void propertiesChanged();
|
||||
void readyChanged();
|
||||
|
||||
private:
|
||||
PwNode* mNode;
|
||||
|
|
|
@ -126,6 +126,29 @@ void PwRegistry::init(PwCore& core) {
|
|||
this->core = &core;
|
||||
this->object = pw_core_get_registry(core.core, PW_VERSION_REGISTRY, 0);
|
||||
pw_registry_add_listener(this->object, &this->listener.hook, &PwRegistry::EVENTS, this);
|
||||
|
||||
QObject::connect(this->core, &PwCore::synced, this, &PwRegistry::onCoreSync);
|
||||
|
||||
qCDebug(logRegistry) << "Registry created. Sending core sync for initial object tracking.";
|
||||
this->coreSyncSeq = this->core->sync(PW_ID_CORE);
|
||||
}
|
||||
|
||||
void PwRegistry::onCoreSync(quint32 id, qint32 seq) {
|
||||
if (id != PW_ID_CORE || seq != this->coreSyncSeq) return;
|
||||
|
||||
switch (this->initState) {
|
||||
case InitState::SendingObjects:
|
||||
qCDebug(logRegistry) << "Initial sync for objects received. Syncing for metadata binding.";
|
||||
this->coreSyncSeq = this->core->sync(PW_ID_CORE);
|
||||
this->initState = InitState::Binding;
|
||||
break;
|
||||
case InitState::Binding:
|
||||
qCInfo(logRegistry) << "Initial state sync complete.";
|
||||
this->initState = InitState::Done;
|
||||
emit this->initialized();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
const pw_registry_events PwRegistry::EVENTS = {
|
||||
|
|
|
@ -116,6 +116,8 @@ class PwRegistry
|
|||
public:
|
||||
void init(PwCore& core);
|
||||
|
||||
[[nodiscard]] bool isInitialized() const { return this->initState == InitState::Done; }
|
||||
|
||||
//QHash<quint32, PwClient*> clients;
|
||||
QHash<quint32, PwMetadata*> metadata;
|
||||
QHash<quint32, PwNode*> nodes;
|
||||
|
@ -132,9 +134,11 @@ signals:
|
|||
void linkAdded(PwLink* link);
|
||||
void linkGroupAdded(PwLinkGroup* group);
|
||||
void metadataAdded(PwMetadata* metadata);
|
||||
void initialized();
|
||||
|
||||
private slots:
|
||||
void onLinkGroupDestroyed(QObject* object);
|
||||
void onCoreSync(quint32 id, qint32 seq);
|
||||
|
||||
private:
|
||||
static const pw_registry_events EVENTS;
|
||||
|
@ -152,6 +156,13 @@ private:
|
|||
|
||||
void addLinkToGroup(PwLink* link);
|
||||
|
||||
enum class InitState : quint8 {
|
||||
SendingObjects,
|
||||
Binding,
|
||||
Done
|
||||
} initState = InitState::SendingObjects;
|
||||
|
||||
qint32 coreSyncSeq = 0;
|
||||
SpaHook listener;
|
||||
};
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ using namespace qs::dbus::dbusmenu;
|
|||
using namespace qs::menu::platform;
|
||||
|
||||
Q_LOGGING_CATEGORY(logStatusNotifierItem, "quickshell.service.sni.item", QtWarningMsg);
|
||||
Q_LOGGING_CATEGORY(logSniMenu, "quickshell.service.sni.item.menu", QtWarningMsg);
|
||||
|
||||
namespace qs::service::sni {
|
||||
|
||||
|
@ -283,7 +282,7 @@ void StatusNotifierItem::onGetAllFailed() const {
|
|||
}
|
||||
|
||||
TrayImageHandle::TrayImageHandle(StatusNotifierItem* item)
|
||||
: QsImageHandle(QQmlImageProviderBase::Pixmap, item)
|
||||
: QsImageHandle(QQmlImageProviderBase::Pixmap)
|
||||
, item(item) {}
|
||||
|
||||
QPixmap
|
||||
|
|
|
@ -21,6 +21,7 @@ qt_add_dbus_interface(DBUS_INTERFACES
|
|||
qt_add_library(quickshell-service-upower STATIC
|
||||
core.cpp
|
||||
device.cpp
|
||||
powerprofiles.cpp
|
||||
${DBUS_INTERFACES}
|
||||
)
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
|
||||
namespace qs::service::upower {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logUPower, "quickshell.service.upower", QtWarningMsg);
|
||||
}
|
||||
|
||||
UPower::UPower() {
|
||||
qCDebug(logUPower) << "Starting UPower Service";
|
||||
|
|
|
@ -54,6 +54,14 @@ private:
|
|||
DBusUPowerService* service = nullptr;
|
||||
};
|
||||
|
||||
///! Provides access to the UPower service.
|
||||
/// An interface to the [UPower daemon], which can be used to
|
||||
/// view battery and power statistics for your computer and
|
||||
/// connected devices.
|
||||
///
|
||||
/// > [!NOTE] The UPower daemon must be installed to use this service.
|
||||
///
|
||||
/// [UPower daemon]: https://upower.freedesktop.org
|
||||
class UPowerQml: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(UPower);
|
||||
|
|
|
@ -15,7 +15,9 @@ using namespace qs::dbus;
|
|||
|
||||
namespace qs::service::upower {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logUPowerDevice, "quickshell.service.upower.device", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString UPowerDeviceState::toString(UPowerDeviceState::Enum status) {
|
||||
switch (status) {
|
||||
|
@ -114,9 +116,7 @@ DBusResult<qreal> DBusDataTransform<PowerPercentage>::fromWire(qreal wire) {
|
|||
|
||||
DBusResult<UPowerDeviceState::Enum>
|
||||
DBusDataTransform<UPowerDeviceState::Enum>::fromWire(quint32 wire) {
|
||||
if (wire != UPowerDeviceType::Battery && wire >= UPowerDeviceState::Unknown
|
||||
&& wire <= UPowerDeviceState::PendingDischarge)
|
||||
{
|
||||
if (wire >= UPowerDeviceState::Unknown && wire <= UPowerDeviceState::PendingDischarge) {
|
||||
return DBusResult(static_cast<UPowerDeviceState::Enum>(wire));
|
||||
}
|
||||
|
||||
|
|
|
@ -154,6 +154,8 @@ class UPowerDevice: public QObject {
|
|||
Q_PROPERTY(bool isLaptopBattery READ isLaptopBattery NOTIFY isLaptopBatteryChanged BINDABLE bindableIsLaptopBattery);
|
||||
/// Native path of the device specific to your OS.
|
||||
Q_PROPERTY(QString nativePath READ nativePath NOTIFY nativePathChanged BINDABLE bindableNativePath);
|
||||
/// Model name of the device. Unlikely to be useful for internal devices.
|
||||
Q_PROPERTY(QString model READ model NOTIFY modelChanged BINDABLE bindableModel);
|
||||
/// If device statistics have been queried for this device yet.
|
||||
/// This will be true for all devices returned from @@UPower.devices, but not the default
|
||||
/// device, which may be returned before it is ready to avoid returning null.
|
||||
|
@ -186,6 +188,7 @@ public:
|
|||
QS_BINDABLE_GETTER(QString, bIconName, iconName, bindableIconName);
|
||||
QS_BINDABLE_GETTER(bool, bIsLaptopBattery, isLaptopBattery, bindableIsLaptopBattery);
|
||||
QS_BINDABLE_GETTER(QString, bNativePath, nativePath, bindableNativePath);
|
||||
QS_BINDABLE_GETTER(QString, bModel, model, bindableModel);
|
||||
QS_BINDABLE_GETTER(bool, bReady, ready, bindableReady);
|
||||
|
||||
signals:
|
||||
|
@ -206,6 +209,7 @@ signals:
|
|||
void iconNameChanged();
|
||||
void isLaptopBatteryChanged();
|
||||
void nativePathChanged();
|
||||
void modelChanged();
|
||||
|
||||
private slots:
|
||||
void onGetAllFinished();
|
||||
|
@ -227,6 +231,7 @@ private:
|
|||
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bIconName, &UPowerDevice::iconNameChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, bool, bIsLaptopBattery, &UPowerDevice::isLaptopBatteryChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bNativePath, &UPowerDevice::nativePathChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bModel, &UPowerDevice::modelChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, bool, bReady, &UPowerDevice::readyChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(UPowerDevice, deviceProperties);
|
||||
|
@ -243,6 +248,7 @@ private:
|
|||
QS_DBUS_PROPERTY_BINDING(UPowerDevice, pHealthPercentage, bHealthPercentage, deviceProperties, "Capacity");
|
||||
QS_DBUS_PROPERTY_BINDING(UPowerDevice, pIconName, bIconName, deviceProperties, "IconName");
|
||||
QS_DBUS_PROPERTY_BINDING(UPowerDevice, pNativePath, bNativePath, deviceProperties, "NativePath");
|
||||
QS_DBUS_PROPERTY_BINDING(UPowerDevice, pModel, bModel, deviceProperties, "Model");
|
||||
// clang-format on
|
||||
|
||||
DBusUPowerDevice* device = nullptr;
|
||||
|
|
|
@ -3,5 +3,6 @@ description = "UPower Service"
|
|||
headers = [
|
||||
"core.hpp",
|
||||
"device.hpp",
|
||||
"powerprofiles.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
213
src/services/upower/powerprofiles.cpp
Normal file
213
src/services/upower/powerprofiles.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
#include "powerprofiles.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbuserror.h>
|
||||
#include <qdbusinterface.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstringliteral.h>
|
||||
|
||||
#include "../../dbus/bus.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
|
||||
namespace qs::service::upower {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logPowerProfiles, "quickshell.service.powerprofiles", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString PowerProfile::toString(PowerProfile::Enum profile) {
|
||||
switch (profile) {
|
||||
case PowerProfile::PowerSaver: return QStringLiteral("PowerSaver");
|
||||
case PowerProfile::Balanced: return QStringLiteral("Balanced");
|
||||
case PowerProfile::Performance: return QStringLiteral("Performance");
|
||||
default: return QStringLiteral("Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
QString PerformanceDegradationReason::toString(PerformanceDegradationReason::Enum reason) {
|
||||
switch (reason) {
|
||||
case PerformanceDegradationReason::LapDetected: return QStringLiteral("LapDetected");
|
||||
case PerformanceDegradationReason::HighTemperature: return QStringLiteral("HighTemperature");
|
||||
default: return QStringLiteral("Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerProfileHold::operator==(const PowerProfileHold& other) const {
|
||||
return other.profile == this->profile && other.applicationId == this->applicationId
|
||||
&& other.reason == this->reason;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const PowerProfileHold& hold) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
|
||||
debug.nospace();
|
||||
debug << "PowerProfileHold(profile=" << hold.profile << ", applicationId=" << hold.applicationId
|
||||
<< ", reason=" << hold.reason << ')';
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
PowerProfiles::PowerProfiles() {
|
||||
qDBusRegisterMetaType<QList<QVariantMap>>();
|
||||
|
||||
this->bHasPerformanceProfile.setBinding([this]() {
|
||||
return this->bProfiles.value().contains(PowerProfile::Performance);
|
||||
});
|
||||
|
||||
qCDebug(logPowerProfiles) << "Starting PowerProfiles Service.";
|
||||
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
|
||||
if (!bus.isConnected()) {
|
||||
qCWarning(logPowerProfiles
|
||||
) << "Could not connect to DBus. PowerProfiles services will not work.";
|
||||
}
|
||||
|
||||
this->service = new QDBusInterface(
|
||||
"org.freedesktop.UPower.PowerProfiles",
|
||||
"/org/freedesktop/UPower/PowerProfiles",
|
||||
"org.freedesktop.UPower.PowerProfiles",
|
||||
bus,
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->service->isValid()) {
|
||||
qCDebug(logPowerProfiles
|
||||
) << "PowerProfilesDaemon is not currently running, attempting to start it.";
|
||||
|
||||
dbus::tryLaunchService(this, bus, "org.freedesktop.UPower.PowerProfiles", [this](bool success) {
|
||||
if (success) {
|
||||
qCDebug(logPowerProfiles) << "Successfully launched PowerProfiles service.";
|
||||
this->init();
|
||||
} else {
|
||||
qCWarning(logPowerProfiles)
|
||||
<< "Could not start PowerProfilesDaemon. The PowerProfiles service will not work.";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this->init();
|
||||
}
|
||||
}
|
||||
|
||||
void PowerProfiles::init() {
|
||||
this->properties.setInterface(this->service);
|
||||
this->properties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void PowerProfiles::setProfile(PowerProfile::Enum profile) {
|
||||
if (profile == PowerProfile::Performance && !this->bHasPerformanceProfile) {
|
||||
qCCritical(logPowerProfiles
|
||||
) << "Cannot request performance profile as it is not present for this device.";
|
||||
return;
|
||||
} else if (profile < PowerProfile::PowerSaver || profile > PowerProfile::Performance) {
|
||||
qCCritical(logPowerProfiles) << "Tried to request invalid power profile" << profile;
|
||||
return;
|
||||
}
|
||||
|
||||
this->bProfile = profile;
|
||||
this->pProfile.write();
|
||||
}
|
||||
|
||||
PowerProfiles* PowerProfiles::instance() {
|
||||
static auto* instance = new PowerProfiles(); // NOLINT
|
||||
return instance;
|
||||
}
|
||||
|
||||
PowerProfilesQml::PowerProfilesQml(QObject* parent): QObject(parent) {
|
||||
auto* instance = PowerProfiles::instance();
|
||||
|
||||
this->bProfile.setBinding([instance]() { return instance->bProfile.value(); });
|
||||
|
||||
this->bHasPerformanceProfile.setBinding([instance]() {
|
||||
return instance->bHasPerformanceProfile.value();
|
||||
});
|
||||
|
||||
this->bDegradationReason.setBinding([instance]() { return instance->bDegradationReason.value(); }
|
||||
);
|
||||
|
||||
this->bHolds.setBinding([instance]() { return instance->bHolds.value(); });
|
||||
}
|
||||
|
||||
} // namespace qs::service::upower
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
using namespace qs::service::upower;
|
||||
|
||||
DBusResult<PowerProfile::Enum> DBusDataTransform<PowerProfile::Enum>::fromWire(const Wire& wire) {
|
||||
if (wire == QStringLiteral("power-saver")) {
|
||||
return PowerProfile::PowerSaver;
|
||||
} else if (wire == QStringLiteral("balanced")) {
|
||||
return PowerProfile::Balanced;
|
||||
} else if (wire == QStringLiteral("performance")) {
|
||||
return PowerProfile::Performance;
|
||||
} else {
|
||||
return QDBusError(QDBusError::InvalidArgs, QString("Invalid PowerProfile: %1").arg(wire));
|
||||
}
|
||||
}
|
||||
|
||||
QString DBusDataTransform<PowerProfile::Enum>::toWire(Data data) {
|
||||
switch (data) {
|
||||
case PowerProfile::PowerSaver: return QStringLiteral("power-saver");
|
||||
case PowerProfile::Balanced: return QStringLiteral("balanced");
|
||||
case PowerProfile::Performance: return QStringLiteral("performance");
|
||||
}
|
||||
}
|
||||
|
||||
DBusResult<QList<PowerProfile::Enum>>
|
||||
DBusDataTransform<QList<PowerProfile::Enum>>::fromWire(const Wire& wire) {
|
||||
QList<PowerProfile::Enum> profiles;
|
||||
|
||||
for (const auto& entry: wire) {
|
||||
auto profile =
|
||||
DBusDataTransform<PowerProfile::Enum>::fromWire(entry.value("Profile").value<QString>());
|
||||
|
||||
if (!profile.isValid()) return profile.error;
|
||||
profiles.append(profile.value);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
DBusResult<PerformanceDegradationReason::Enum>
|
||||
DBusDataTransform<PerformanceDegradationReason::Enum>::fromWire(const Wire& wire) {
|
||||
if (wire.isEmpty()) {
|
||||
return PerformanceDegradationReason::None;
|
||||
} else if (wire == QStringLiteral("lap-detected")) {
|
||||
return PerformanceDegradationReason::LapDetected;
|
||||
} else if (wire == QStringLiteral("high-operating-temperature")) {
|
||||
return PerformanceDegradationReason::HighTemperature;
|
||||
} else {
|
||||
return QDBusError(
|
||||
QDBusError::InvalidArgs,
|
||||
QString("Invalid PerformanceDegradationReason: %1").arg(wire)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DBusResult<QList<PowerProfileHold>>
|
||||
DBusDataTransform<QList<PowerProfileHold>>::fromWire(const Wire& wire) {
|
||||
QList<PowerProfileHold> holds;
|
||||
|
||||
for (const auto& entry: wire) {
|
||||
auto profile =
|
||||
DBusDataTransform<PowerProfile::Enum>::fromWire(entry.value("Profile").value<QString>());
|
||||
|
||||
if (!profile.isValid()) return profile.error;
|
||||
|
||||
auto applicationId = entry.value("ApplicationId").value<QString>();
|
||||
auto reason = entry.value("Reason").value<QString>();
|
||||
|
||||
holds.append(PowerProfileHold(profile.value, applicationId, reason));
|
||||
}
|
||||
|
||||
return holds;
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
237
src/services/upower/powerprofiles.hpp
Normal file
237
src/services/upower/powerprofiles.hpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusinterface.h>
|
||||
#include <qdebug.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
|
||||
namespace qs::service::upower {
|
||||
|
||||
///! Power profile exposed by the PowerProfiles service.
|
||||
/// See @@PowerProfiles.
|
||||
class PowerProfile: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// This profile will limit system performance in order to save power.
|
||||
PowerSaver = 0,
|
||||
/// This profile is the default, and will attempt to strike a balance
|
||||
/// between performance and power consumption.
|
||||
Balanced = 1,
|
||||
/// This profile will maximize performance at the cost of power consumption.
|
||||
Performance = 2,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
|
||||
Q_INVOKABLE static QString toString(qs::service::upower::PowerProfile::Enum profile);
|
||||
};
|
||||
|
||||
///! Reason for performance degradation exposed by the PowerProfiles service.
|
||||
/// See @@PowerProfiles.degradationReason for more information.
|
||||
class PerformanceDegradationReason: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// Performance has not been degraded in a way power-profiles-daemon can detect.
|
||||
None = 0,
|
||||
/// Performance has been reduced due to the computer's lap detection function,
|
||||
/// which attempts to keep the computer from getting too hot while on your lap.
|
||||
LapDetected = 1,
|
||||
/// Performance has been reduced due to high system temperatures.
|
||||
HighTemperature = 2,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
|
||||
// clang-format off
|
||||
Q_INVOKABLE static QString toString(qs::service::upower::PerformanceDegradationReason::Enum reason);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
class PowerProfileHold;
|
||||
|
||||
} // namespace qs::service::upower
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::service::upower::PowerProfile::Enum> {
|
||||
using Wire = QString;
|
||||
using Data = qs::service::upower::PowerProfile::Enum;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
static Wire toWire(Data data);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<QList<qs::service::upower::PowerProfile::Enum>> {
|
||||
using Wire = QList<QVariantMap>;
|
||||
using Data = QList<qs::service::upower::PowerProfile::Enum>;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::service::upower::PerformanceDegradationReason::Enum> {
|
||||
using Wire = QString;
|
||||
using Data = qs::service::upower::PerformanceDegradationReason::Enum;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<QList<qs::service::upower::PowerProfileHold>> {
|
||||
using Wire = QList<QVariantMap>;
|
||||
using Data = QList<qs::service::upower::PowerProfileHold>;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::service::upower {
|
||||
|
||||
// docgen can't hit gadgets yet
|
||||
class PowerProfileHold {
|
||||
Q_GADGET;
|
||||
QML_VALUE_TYPE(powerProfileHold);
|
||||
Q_PROPERTY(qs::service::upower::PowerProfile::Enum profile MEMBER profile CONSTANT);
|
||||
Q_PROPERTY(QString applicationId MEMBER applicationId CONSTANT);
|
||||
Q_PROPERTY(QString reason MEMBER reason CONSTANT);
|
||||
|
||||
public:
|
||||
explicit PowerProfileHold() = default;
|
||||
explicit PowerProfileHold(PowerProfile::Enum profile, QString applicationId, QString reason)
|
||||
: profile(profile)
|
||||
, applicationId(std::move(applicationId))
|
||||
, reason(std::move(reason)) {}
|
||||
|
||||
PowerProfile::Enum profile = PowerProfile::Balanced;
|
||||
QString applicationId;
|
||||
QString reason;
|
||||
|
||||
[[nodiscard]] bool operator==(const PowerProfileHold& other) const;
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const PowerProfileHold& hold);
|
||||
|
||||
class PowerProfiles: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
void setProfile(PowerProfile::Enum profile);
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(PowerProfiles, PowerProfile::Enum, bProfile, PowerProfile::Balanced);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, bool, bHasPerformanceProfile);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, PerformanceDegradationReason::Enum, bDegradationReason);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, QList<PowerProfileHold>, bHolds);
|
||||
// clang-format on
|
||||
|
||||
static PowerProfiles* instance();
|
||||
|
||||
private:
|
||||
explicit PowerProfiles();
|
||||
void init();
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, QList<PowerProfile::Enum>, bProfiles);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(PowerProfiles, properties);
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pProfile, bProfile, properties, "ActiveProfile");
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pProfiles, bProfiles, properties, "Profiles");
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pPerformanceDegraded, bDegradationReason, properties, "PerformanceDegraded");
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pHolds, bHolds, properties, "ActiveProfileHolds");
|
||||
// clang-format on
|
||||
|
||||
QDBusInterface* service = nullptr;
|
||||
};
|
||||
|
||||
///! Provides access to the Power Profiles service.
|
||||
/// An interface to the UPower [power profiles daemon], which can be
|
||||
/// used to view and manage power profiles.
|
||||
///
|
||||
/// > [!NOTE] The power profiles daemon must be installed to use this service.
|
||||
/// > Installing UPower does not necessarily install the power profiles daemon.
|
||||
///
|
||||
/// [power profiles daemon]: https://gitlab.freedesktop.org/upower/power-profiles-daemon
|
||||
class PowerProfilesQml: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(PowerProfiles);
|
||||
QML_SINGLETON;
|
||||
// clang-format off
|
||||
/// The current power profile.
|
||||
///
|
||||
/// This property may be set to change the system's power profile, however
|
||||
/// it cannot be set to `Performance` unless @@hasPerformanceProfile is true.
|
||||
Q_PROPERTY(qs::service::upower::PowerProfile::Enum profile READ default WRITE setProfile NOTIFY profileChanged BINDABLE bindableProfile);
|
||||
/// If the system has a performance profile.
|
||||
///
|
||||
/// If this property is false, your system does not have a performance
|
||||
/// profile known to power-profiles-daemon.
|
||||
Q_PROPERTY(bool hasPerformanceProfile READ default NOTIFY hasPerformanceProfileChanged BINDABLE bindableHasPerformanceProfile);
|
||||
/// If power-profiles-daemon detects degraded system performance, the reason
|
||||
/// for the degradation will be present here.
|
||||
Q_PROPERTY(qs::service::upower::PerformanceDegradationReason::Enum degradationReason READ default NOTIFY degradationReasonChanged BINDABLE bindableDegradationReason);
|
||||
/// Power profile holds created by other applications.
|
||||
///
|
||||
/// This property returns a `powerProfileHold` object, which has the following properties.
|
||||
/// - `profile` - The @@PowerProfile held by the application.
|
||||
/// - `applicationId` - A string identifying the application
|
||||
/// - `reason` - The reason the application has given for holding the profile.
|
||||
///
|
||||
/// Applications may "hold" a power profile in place for their lifetime, such
|
||||
/// as a game holding Performance mode or a system daemon holding Power Saver mode
|
||||
/// when reaching a battery threshold. If the user selects a different profile explicitly
|
||||
/// (e.g. by setting @@profile$) all holds will be removed.
|
||||
///
|
||||
/// Multiple applications may hold a power profile, however if multiple applications request
|
||||
/// profiles than `PowerSaver` will win over `Performance`. Only `Performance` and `PowerSaver`
|
||||
/// profiles may be held.
|
||||
Q_PROPERTY(QList<qs::service::upower::PowerProfileHold> holds READ default NOTIFY holdsChanged BINDABLE bindableHolds);
|
||||
// clang-format on
|
||||
|
||||
signals:
|
||||
void profileChanged();
|
||||
void hasPerformanceProfileChanged();
|
||||
void degradationReasonChanged();
|
||||
void holdsChanged();
|
||||
|
||||
public:
|
||||
explicit PowerProfilesQml(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QBindable<PowerProfile::Enum> bindableProfile() const { return &this->bProfile; }
|
||||
|
||||
static void setProfile(PowerProfile::Enum profile) {
|
||||
PowerProfiles::instance()->setProfile(profile);
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<bool> bindableHasPerformanceProfile() const {
|
||||
return &this->bHasPerformanceProfile;
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<PerformanceDegradationReason::Enum> bindableDegradationReason() const {
|
||||
return &this->bDegradationReason;
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<QList<PowerProfileHold>> bindableHolds() const { return &this->bHolds; }
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, PowerProfile::Enum, bProfile, &PowerProfilesQml::profileChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, bool, bHasPerformanceProfile, &PowerProfilesQml::hasPerformanceProfileChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, PerformanceDegradationReason::Enum, bDegradationReason, &PowerProfilesQml::degradationReasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, QList<PowerProfileHold>, bHolds, &PowerProfilesQml::holdsChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace qs::service::upower
|
|
@ -28,7 +28,7 @@ qs_add_pchset(wayland-protocol
|
|||
<qstring.h>
|
||||
)
|
||||
|
||||
function (wl_proto target name path)
|
||||
function (wl_proto target name dir)
|
||||
set(PROTO_BUILD_PATH ${CMAKE_CURRENT_BINARY_DIR}/wl-proto/${name})
|
||||
make_directory(${PROTO_BUILD_PATH})
|
||||
|
||||
|
@ -36,39 +36,38 @@ function (wl_proto target name path)
|
|||
set(WS_CLIENT_CODE "${PROTO_BUILD_PATH}/wayland-${name}.c")
|
||||
set(QWS_CLIENT_HEADER "${PROTO_BUILD_PATH}/qwayland-${name}.h")
|
||||
set(QWS_CLIENT_CODE "${PROTO_BUILD_PATH}/qwayland-${name}.cpp")
|
||||
set(PATH "${dir}/${name}.xml")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${WS_CLIENT_HEADER}"
|
||||
COMMAND Wayland::Scanner client-header "${path}" "${WS_CLIENT_HEADER}"
|
||||
DEPENDS Wayland::Scanner "${path}"
|
||||
COMMAND Wayland::Scanner client-header "${PATH}" "${WS_CLIENT_HEADER}"
|
||||
DEPENDS Wayland::Scanner "${PATH}"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${WS_CLIENT_CODE}"
|
||||
COMMAND Wayland::Scanner private-code "${path}" "${WS_CLIENT_CODE}"
|
||||
DEPENDS Wayland::Scanner "${path}"
|
||||
COMMAND Wayland::Scanner private-code "${PATH}" "${WS_CLIENT_CODE}"
|
||||
DEPENDS Wayland::Scanner "${PATH}"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${QWS_CLIENT_HEADER}"
|
||||
COMMAND Qt6::qtwaylandscanner client-header "${path}" > "${QWS_CLIENT_HEADER}"
|
||||
DEPENDS Qt6::qtwaylandscanner "${path}"
|
||||
COMMAND Qt6::qtwaylandscanner client-header "${PATH}" > "${QWS_CLIENT_HEADER}"
|
||||
DEPENDS Qt6::qtwaylandscanner "${PATH}"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${QWS_CLIENT_CODE}"
|
||||
COMMAND Qt6::qtwaylandscanner client-code "${path}" > "${QWS_CLIENT_CODE}"
|
||||
DEPENDS Qt6::qtwaylandscanner "${path}"
|
||||
COMMAND Qt6::qtwaylandscanner client-code "${PATH}" > "${QWS_CLIENT_CODE}"
|
||||
DEPENDS Qt6::qtwaylandscanner "${PATH}"
|
||||
)
|
||||
|
||||
add_library(wl-proto-${name}-wl STATIC ${WS_CLIENT_HEADER} ${WS_CLIENT_CODE})
|
||||
add_library(wl-proto-${name} STATIC ${QWS_CLIENT_HEADER} ${QWS_CLIENT_CODE})
|
||||
add_library(${target} STATIC ${QWS_CLIENT_HEADER} ${QWS_CLIENT_CODE})
|
||||
|
||||
target_include_directories(wl-proto-${name} INTERFACE ${PROTO_BUILD_PATH})
|
||||
target_link_libraries(wl-proto-${name} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate)
|
||||
qs_pch(wl-proto-${name} SET wayland-protocol)
|
||||
|
||||
target_link_libraries(${target} PRIVATE wl-proto-${name})
|
||||
target_include_directories(${target} INTERFACE ${PROTO_BUILD_PATH})
|
||||
target_link_libraries(${target} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate)
|
||||
qs_pch(${target} SET wayland-protocol)
|
||||
endfunction()
|
||||
|
||||
# -----
|
||||
|
@ -104,6 +103,13 @@ if (WAYLAND_TOPLEVEL_MANAGEMENT)
|
|||
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ToplevelManagement)
|
||||
endif()
|
||||
|
||||
if (SCREENCOPY)
|
||||
add_subdirectory(buffer)
|
||||
add_subdirectory(screencopy)
|
||||
list(APPEND WAYLAND_MODULES Quickshell.Wayland._Screencopy)
|
||||
endif()
|
||||
|
||||
|
||||
if (HYPRLAND)
|
||||
add_subdirectory(hyprland)
|
||||
endif()
|
||||
|
|
18
src/wayland/buffer/CMakeLists.txt
Normal file
18
src/wayland/buffer/CMakeLists.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
|
||||
|
||||
qt_add_library(quickshell-wayland-buffer STATIC
|
||||
manager.cpp
|
||||
dmabuf.cpp
|
||||
shm.cpp
|
||||
)
|
||||
|
||||
wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
|
||||
|
||||
target_link_libraries(quickshell-wayland-buffer PRIVATE
|
||||
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
||||
PkgConfig::dmabuf-deps
|
||||
wlp-linux-dmabuf
|
||||
)
|
||||
|
||||
qs_pch(quickshell-wayland-buffer SET large)
|
609
src/wayland/buffer/dmabuf.cpp
Normal file
609
src/wayland/buffer/dmabuf.cpp
Normal file
|
@ -0,0 +1,609 @@
|
|||
#include "dmabuf.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GLES3/gl32.h>
|
||||
#include <fcntl.h>
|
||||
#include <gbm.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qopenglcontext.h>
|
||||
#include <qopenglcontext_platform.h>
|
||||
#include <qpair.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qscopedpointer.h>
|
||||
#include <qsgtexture_platform.h>
|
||||
#include <qwayland-linux-dmabuf-v1.h>
|
||||
#include <qwaylandclientextension.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
||||
#include <wayland-util.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
#include "../../core/stacklist.hpp"
|
||||
#include "manager.hpp"
|
||||
#include "manager_p.hpp"
|
||||
|
||||
namespace qs::wayland::buffer::dmabuf {
|
||||
|
||||
namespace {
|
||||
|
||||
Q_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg);
|
||||
|
||||
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
|
||||
|
||||
} // namespace
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
|
||||
debug << fourcc.cStr();
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const FourCCModStr& fourcc) {
|
||||
debug << fourcc.cStr();
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
debug.nospace();
|
||||
|
||||
if (buffer) {
|
||||
debug << "WlDmaBuffer(" << static_cast<const void*>(buffer) << ", size=" << buffer->width << 'x'
|
||||
<< buffer->height << ", format=" << FourCCStr(buffer->format) << ", modifier=`"
|
||||
<< FourCCModStr(buffer->modifier) << "`)";
|
||||
} else {
|
||||
debug << "WlDmaBuffer(0x0)";
|
||||
}
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
GbmDeviceHandle::~GbmDeviceHandle() {
|
||||
if (device) {
|
||||
MANAGER->unrefGbmDevice(this->device);
|
||||
}
|
||||
}
|
||||
|
||||
// This will definitely backfire later
|
||||
void LinuxDmabufFormatSelection::ensureSorted() {
|
||||
if (this->sorted) return;
|
||||
auto beginIter = this->formats.begin();
|
||||
|
||||
auto xrgbIter = std::ranges::find_if(this->formats, [](const auto& format) {
|
||||
return format.first == DRM_FORMAT_XRGB8888;
|
||||
});
|
||||
|
||||
if (xrgbIter != this->formats.end()) {
|
||||
std::swap(*beginIter, *xrgbIter);
|
||||
++beginIter;
|
||||
}
|
||||
|
||||
auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) {
|
||||
return format.first == DRM_FORMAT_ARGB8888;
|
||||
});
|
||||
|
||||
if (argbIter != this->formats.end()) std::swap(*beginIter, *argbIter);
|
||||
|
||||
this->sorted = true;
|
||||
}
|
||||
|
||||
LinuxDmabufFeedback::LinuxDmabufFeedback(::zwp_linux_dmabuf_feedback_v1* feedback)
|
||||
: zwp_linux_dmabuf_feedback_v1(feedback) {}
|
||||
|
||||
LinuxDmabufFeedback::~LinuxDmabufFeedback() { this->destroy(); }
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_format_table(int32_t fd, uint32_t size) {
|
||||
this->formatTableSize = size;
|
||||
|
||||
this->formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
|
||||
if (this->formatTable == MAP_FAILED) {
|
||||
this->formatTable = nullptr;
|
||||
qCFatal(logDmabuf) << "Failed to mmap format table.";
|
||||
}
|
||||
|
||||
qCDebug(logDmabuf) << "Got format table";
|
||||
}
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_main_device(wl_array* device) {
|
||||
if (device->size != sizeof(dev_t)) {
|
||||
qCFatal(logDmabuf) << "The size of dev_t used by the compositor and quickshell is mismatched. "
|
||||
"Try recompiling both.";
|
||||
}
|
||||
|
||||
this->mainDevice = *reinterpret_cast<dev_t*>(device->data);
|
||||
qCDebug(logDmabuf) << "Got main device id" << this->mainDevice;
|
||||
}
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_target_device(wl_array* device) {
|
||||
if (device->size != sizeof(dev_t)) {
|
||||
qCFatal(logDmabuf) << "The size of dev_t used by the compositor and quickshell is mismatched. "
|
||||
"Try recompiling both.";
|
||||
}
|
||||
|
||||
auto& tranche = this->tranches.emplaceBack();
|
||||
tranche.device = *reinterpret_cast<dev_t*>(device->data);
|
||||
qCDebug(logDmabuf) << "Got target device id" << this->mainDevice;
|
||||
}
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_flags(uint32_t flags) {
|
||||
this->tranches.back().flags = flags;
|
||||
qCDebug(logDmabuf) << "Got target device flags" << flags;
|
||||
}
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_formats(wl_array* indices) {
|
||||
struct FormatTableEntry {
|
||||
uint32_t format;
|
||||
uint32_t padding;
|
||||
uint64_t modifier;
|
||||
};
|
||||
|
||||
static_assert(sizeof(FormatTableEntry) == 16, "Format table entry was not packed to 16 bytes.");
|
||||
|
||||
if (this->formatTable == nullptr) {
|
||||
qCFatal(logDmabuf) << "Received tranche formats before format table.";
|
||||
}
|
||||
|
||||
auto& tranche = this->tranches.back();
|
||||
|
||||
auto* table = reinterpret_cast<FormatTableEntry*>(this->formatTable);
|
||||
auto* indexTable = reinterpret_cast<uint16_t*>(indices->data);
|
||||
auto indexTableLength = indices->size / sizeof(uint16_t);
|
||||
|
||||
uint32_t lastFormat = 0;
|
||||
LinuxDmabufModifiers* modifiers = nullptr;
|
||||
|
||||
for (uint16_t ti = 0; ti != indexTableLength; ++ti) {
|
||||
auto i = indexTable[ti]; // NOLINT
|
||||
const auto& entry = table[i]; // NOLINT
|
||||
|
||||
// Compositors usually send a single format's modifiers as a block.
|
||||
if (!modifiers || entry.format != lastFormat) {
|
||||
lastFormat = entry.format;
|
||||
|
||||
auto modifiersIter = std::ranges::find_if(tranche.formats.formats, [&](const auto& pair) {
|
||||
return pair.first == entry.format;
|
||||
});
|
||||
|
||||
if (modifiersIter == tranche.formats.formats.end()) {
|
||||
tranche.formats.formats.push(qMakePair(entry.format, LinuxDmabufModifiers()));
|
||||
modifiers = &(--tranche.formats.formats.end())->second;
|
||||
} else {
|
||||
modifiers = &modifiersIter->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.modifier == DRM_FORMAT_MOD_INVALID) {
|
||||
modifiers->implicit = true;
|
||||
} else {
|
||||
modifiers->modifiers.push(entry.modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_tranche_done() {
|
||||
qCDebug(logDmabuf) << "Got tranche end.";
|
||||
}
|
||||
|
||||
void LinuxDmabufFeedback::zwp_linux_dmabuf_feedback_v1_done() {
|
||||
qCDebug(logDmabuf) << "Got feedback done.";
|
||||
|
||||
if (this->formatTable) {
|
||||
munmap(this->formatTable, this->formatTableSize);
|
||||
this->formatTable = nullptr;
|
||||
}
|
||||
|
||||
if (logDmabuf().isDebugEnabled()) {
|
||||
qCDebug(logDmabuf) << "Dmabuf tranches:";
|
||||
|
||||
for (auto& tranche: this->tranches) {
|
||||
qCDebug(logDmabuf) << " Tranche on device" << tranche.device;
|
||||
|
||||
// will be sorted on first use otherwise
|
||||
tranche.formats.ensureSorted();
|
||||
|
||||
for (auto& [format, modifiers]: tranche.formats.formats) {
|
||||
qCDebug(logDmabuf) << " Format" << FourCCStr(format);
|
||||
|
||||
if (modifiers.implicit) {
|
||||
qCDebug(logDmabuf) << " Implicit Modifier";
|
||||
}
|
||||
|
||||
for (const auto& modifier: modifiers.modifiers) {
|
||||
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy tranches to the manager. If the compositor ever updates
|
||||
// our tranches, we'll start from a clean slate.
|
||||
MANAGER->tranches = this->tranches;
|
||||
this->tranches.clear();
|
||||
|
||||
MANAGER->feedbackDone();
|
||||
}
|
||||
|
||||
LinuxDmabufManager::LinuxDmabufManager(WlBufferManagerPrivate* manager)
|
||||
: QWaylandClientExtensionTemplate(5)
|
||||
, manager(manager) {
|
||||
MANAGER = this;
|
||||
this->initialize();
|
||||
|
||||
if (this->isActive()) {
|
||||
qCDebug(logDmabuf) << "Requesting default dmabuf feedback...";
|
||||
new LinuxDmabufFeedback(this->get_default_feedback());
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxDmabufManager::feedbackDone() { this->manager->dmabufReady(); }
|
||||
|
||||
GbmDeviceHandle LinuxDmabufManager::getGbmDevice(dev_t handle) {
|
||||
struct DrmFree {
|
||||
static void cleanup(drmDevice* d) { drmFreeDevice(&d); }
|
||||
};
|
||||
|
||||
std::string renderNodeStorage;
|
||||
std::string* renderNode = nullptr;
|
||||
|
||||
auto sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
|
||||
return d.handle == handle;
|
||||
});
|
||||
|
||||
if (sharedDevice != this->gbmDevices.end()) {
|
||||
renderNode = &sharedDevice->renderNode;
|
||||
} else {
|
||||
drmDevice* drmDevPtr = nullptr;
|
||||
if (auto error = drmGetDeviceFromDevId(handle, 0, &drmDevPtr); error != 0) {
|
||||
qCWarning(logDmabuf) << "Failed to get drm device information from handle:"
|
||||
<< qt_error_string(error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto drmDev = QScopedPointer<drmDevice, DrmFree>(drmDevPtr);
|
||||
|
||||
if (!(drmDev->available_nodes & (1 << DRM_NODE_RENDER))) {
|
||||
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
|
||||
renderNode = &renderNodeStorage;
|
||||
sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
|
||||
return d.renderNode == renderNodeStorage;
|
||||
});
|
||||
}
|
||||
|
||||
if (sharedDevice != this->gbmDevices.end()) {
|
||||
qCDebug(logDmabuf) << "Used existing GBM device on render node" << *renderNode;
|
||||
++sharedDevice->refcount;
|
||||
return sharedDevice->device;
|
||||
} else {
|
||||
auto fd = open(renderNode->c_str(), O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
qCDebug(logDmabuf) << "Could not open render node" << *renderNode << ":"
|
||||
<< qt_error_string(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* device = gbm_create_device(fd);
|
||||
|
||||
if (!device) {
|
||||
qCDebug(logDmabuf) << "Failed to create GBM device from render node" << *renderNode;
|
||||
close(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qCDebug(logDmabuf) << "Created GBM device on render node" << *renderNode;
|
||||
|
||||
this->gbmDevices.push_back({
|
||||
.handle = handle,
|
||||
.renderNode = std::move(renderNodeStorage),
|
||||
.device = device,
|
||||
.refcount = 1,
|
||||
});
|
||||
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxDmabufManager::unrefGbmDevice(gbm_device* device) {
|
||||
auto iter = std::ranges::find_if(this->gbmDevices, [device](const SharedGbmDevice& d) {
|
||||
return d.device == device;
|
||||
});
|
||||
if (iter == this->gbmDevices.end()) return;
|
||||
|
||||
qCDebug(logDmabuf) << "Lost reference to GBM device" << device;
|
||||
|
||||
if (--iter->refcount == 0) {
|
||||
auto fd = gbm_device_get_fd(iter->device);
|
||||
gbm_device_destroy(iter->device);
|
||||
close(fd);
|
||||
|
||||
this->gbmDevices.erase(iter);
|
||||
qCDebug(logDmabuf) << "Destroyed GBM device" << device;
|
||||
}
|
||||
}
|
||||
|
||||
GbmDeviceHandle LinuxDmabufManager::dupHandle(const GbmDeviceHandle& handle) {
|
||||
if (!handle) return GbmDeviceHandle();
|
||||
|
||||
auto iter = std::ranges::find_if(this->gbmDevices, [&handle](const SharedGbmDevice& d) {
|
||||
return d.device == *handle;
|
||||
});
|
||||
if (iter == this->gbmDevices.end()) return GbmDeviceHandle();
|
||||
|
||||
qCDebug(logDmabuf) << "Duplicated GBM device handle" << *handle;
|
||||
++iter->refcount;
|
||||
return GbmDeviceHandle(*handle);
|
||||
}
|
||||
|
||||
WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
|
||||
for (auto& tranche: this->tranches) {
|
||||
if (request.dmabuf.device != 0 && tranche.device != request.dmabuf.device) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LinuxDmabufFormatSelection formats;
|
||||
for (const auto& format: request.dmabuf.formats) {
|
||||
if (!format.modifiers.isEmpty()) {
|
||||
formats.formats.push(
|
||||
qMakePair(format.format, LinuxDmabufModifiers {.modifiers = format.modifiers})
|
||||
);
|
||||
} else {
|
||||
for (const auto& trancheFormat: tranche.formats.formats) {
|
||||
if (trancheFormat.first == format.format) {
|
||||
formats.formats.push(trancheFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (formats.formats.isEmpty()) continue;
|
||||
formats.ensureSorted();
|
||||
|
||||
auto gbmDevice = this->getGbmDevice(tranche.device);
|
||||
|
||||
if (!gbmDevice) {
|
||||
qCWarning(logDmabuf) << "Hit unusable tranche device while trying to create dmabuf.";
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& [format, modifiers]: formats.formats) {
|
||||
if (auto* buf =
|
||||
this->createDmabuf(gbmDevice, format, modifiers, request.width, request.height))
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qCWarning(logDmabuf) << "Unable to create dmabuf for request: No matching formats.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WlBuffer* LinuxDmabufManager::createDmabuf(
|
||||
GbmDeviceHandle& device,
|
||||
uint32_t format,
|
||||
const LinuxDmabufModifiers& modifiers,
|
||||
uint32_t width,
|
||||
uint32_t height
|
||||
) {
|
||||
auto buffer = std::unique_ptr<WlDmaBuffer>(new WlDmaBuffer());
|
||||
auto& bo = buffer->bo;
|
||||
|
||||
const uint32_t flags = GBM_BO_USE_RENDERING;
|
||||
|
||||
if (modifiers.modifiers.isEmpty()) {
|
||||
if (!modifiers.implicit) {
|
||||
qCritical(logDmabuf
|
||||
) << "Failed to create gbm_bo: format supports no implicit OR explicit modifiers.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qCDebug(logDmabuf) << "Creating gbm_bo without modifiers...";
|
||||
bo = gbm_bo_create(*device, width, height, format, flags);
|
||||
} else {
|
||||
qCDebug(logDmabuf) << "Creating gbm_bo with modifiers...";
|
||||
|
||||
STACKLIST_VLA_VIEW(uint64_t, modifiers.modifiers, modifiersData);
|
||||
|
||||
bo = gbm_bo_create_with_modifiers2(
|
||||
*device,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
modifiersData,
|
||||
modifiers.modifiers.length(),
|
||||
flags
|
||||
);
|
||||
}
|
||||
|
||||
if (!bo) {
|
||||
qCritical(logDmabuf) << "Failed to create gbm_bo.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
buffer->planeCount = gbm_bo_get_plane_count(bo);
|
||||
buffer->planes = new WlDmaBuffer::Plane[buffer->planeCount]();
|
||||
buffer->modifier = gbm_bo_get_modifier(bo);
|
||||
|
||||
auto params = QtWayland::zwp_linux_buffer_params_v1(this->create_params());
|
||||
|
||||
for (auto i = 0; i < buffer->planeCount; ++i) {
|
||||
auto& plane = buffer->planes[i]; // NOLINT
|
||||
plane.fd = gbm_bo_get_fd_for_plane(bo, i);
|
||||
|
||||
if (plane.fd < 0) {
|
||||
qCritical(logDmabuf) << "Failed to get gbm_bo fd for plane" << i << qt_error_string(plane.fd);
|
||||
params.destroy();
|
||||
gbm_bo_destroy(bo);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
plane.stride = gbm_bo_get_stride_for_plane(bo, i);
|
||||
plane.offset = gbm_bo_get_offset(bo, i);
|
||||
|
||||
params.add(
|
||||
plane.fd,
|
||||
i,
|
||||
plane.offset,
|
||||
plane.stride,
|
||||
buffer->modifier >> 32,
|
||||
buffer->modifier & 0xffffffff
|
||||
);
|
||||
}
|
||||
|
||||
buffer->mBuffer =
|
||||
params.create_immed(static_cast<int32_t>(width), static_cast<int32_t>(height), format, 0);
|
||||
params.destroy();
|
||||
|
||||
buffer->device = this->dupHandle(device);
|
||||
buffer->width = width;
|
||||
buffer->height = height;
|
||||
buffer->format = format;
|
||||
|
||||
qCDebug(logDmabuf) << "Created dmabuf" << buffer.get();
|
||||
return buffer.release();
|
||||
}
|
||||
|
||||
WlDmaBuffer::WlDmaBuffer(WlDmaBuffer&& other) noexcept
|
||||
: device(std::move(other.device))
|
||||
, bo(other.bo)
|
||||
, mBuffer(other.mBuffer)
|
||||
, planes(other.planes) {
|
||||
other.mBuffer = nullptr;
|
||||
other.bo = nullptr;
|
||||
other.planeCount = 0;
|
||||
}
|
||||
|
||||
WlDmaBuffer& WlDmaBuffer::operator=(WlDmaBuffer&& other) noexcept {
|
||||
this->~WlDmaBuffer();
|
||||
new (this) WlDmaBuffer(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
WlDmaBuffer::~WlDmaBuffer() {
|
||||
if (this->mBuffer) {
|
||||
wl_buffer_destroy(this->mBuffer);
|
||||
}
|
||||
|
||||
if (this->bo) {
|
||||
gbm_bo_destroy(this->bo);
|
||||
qCDebug(logDmabuf) << "Destroyed" << this << "freeing bo" << this->bo;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < this->planeCount; ++i) {
|
||||
const auto& plane = this->planes[i]; // NOLINT
|
||||
if (plane.fd) close(plane.fd);
|
||||
}
|
||||
|
||||
delete[] this->planes;
|
||||
}
|
||||
|
||||
bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
|
||||
if (request.width != this->width || request.height != this->height) return false;
|
||||
|
||||
auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [&](const auto& format) {
|
||||
return format.format == this->format
|
||||
&& (format.modifiers.isEmpty()
|
||||
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
|
||||
});
|
||||
|
||||
return matchingFormat != request.dmabuf.formats.end();
|
||||
}
|
||||
|
||||
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
|
||||
static auto* glEGLImageTargetTexture2DOES = []() {
|
||||
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
|
||||
eglGetProcAddress("glEGLImageTargetTexture2DOES")
|
||||
);
|
||||
|
||||
if (!fn) {
|
||||
qCFatal(logDmabuf) << "Failed to create QSG texture from WlDmaBuffer: "
|
||||
"glEGLImageTargetTexture2DOES is missing.";
|
||||
}
|
||||
|
||||
return fn;
|
||||
}();
|
||||
|
||||
auto* context = QOpenGLContext::currentContext();
|
||||
if (!context) {
|
||||
qCFatal(logDmabuf) << "Failed to create QSG texture from WlDmaBuffer: No GL context.";
|
||||
}
|
||||
|
||||
auto* qEglContext = context->nativeInterface<QNativeInterface::QEGLContext>();
|
||||
if (!qEglContext) {
|
||||
qCFatal(logDmabuf) << "Failed to create QSG texture from WlDmaBuffer: No EGL context.";
|
||||
}
|
||||
|
||||
auto* display = qEglContext->display();
|
||||
|
||||
// clang-format off
|
||||
auto attribs = std::array<EGLAttrib, 6 * 2 + 1> {
|
||||
EGL_WIDTH, this->width,
|
||||
EGL_HEIGHT, this->height,
|
||||
EGL_LINUX_DRM_FOURCC_EXT, this->format,
|
||||
EGL_DMA_BUF_PLANE0_FD_EXT, this->planes[0].fd, // NOLINT
|
||||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, this->planes[0].offset, // NOLINT
|
||||
EGL_DMA_BUF_PLANE0_PITCH_EXT, this->planes[0].stride, // NOLINT
|
||||
EGL_NONE
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
auto* eglImage =
|
||||
eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
|
||||
|
||||
if (eglImage == EGL_NO_IMAGE) {
|
||||
qFatal() << "failed to make egl image" << eglGetError();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
window->beginExternalCommands();
|
||||
GLuint glTexture = 0;
|
||||
glGenTextures(1, &glTexture);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, glTexture);
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
window->endExternalCommands();
|
||||
|
||||
auto* qsgTexture = QNativeInterface::QSGOpenGLTexture::fromNative(
|
||||
glTexture,
|
||||
window,
|
||||
QSize(static_cast<int>(this->width), static_cast<int>(this->height))
|
||||
);
|
||||
|
||||
auto* tex = new WlDmaBufferQSGTexture(eglImage, glTexture, qsgTexture);
|
||||
qCDebug(logDmabuf) << "Created WlDmaBufferQSGTexture" << tex << "from" << this;
|
||||
return tex;
|
||||
}
|
||||
|
||||
WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
|
||||
auto* context = QOpenGLContext::currentContext();
|
||||
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();
|
||||
|
||||
if (this->glTexture) glDeleteTextures(1, &this->glTexture);
|
||||
if (this->eglImage) eglDestroyImage(display, this->eglImage);
|
||||
delete this->qsgTexture;
|
||||
|
||||
qCDebug(logDmabuf) << "WlDmaBufferQSGTexture" << this << "destroyed.";
|
||||
}
|
||||
|
||||
} // namespace qs::wayland::buffer::dmabuf
|
235
src/wayland/buffer/dmabuf.hpp
Normal file
235
src/wayland/buffer/dmabuf.hpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <gbm.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qhash.h>
|
||||
#include <qlist.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qsgtexture.h>
|
||||
#include <qsize.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qwayland-linux-dmabuf-v1.h>
|
||||
#include <qwaylandclientextension.h>
|
||||
#include <sys/types.h>
|
||||
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
||||
#include <wayland-util.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
#include "manager.hpp"
|
||||
#include "qsg.hpp"
|
||||
|
||||
namespace qs::wayland::buffer {
|
||||
class WlBufferManagerPrivate;
|
||||
}
|
||||
|
||||
namespace qs::wayland::buffer::dmabuf {
|
||||
|
||||
class LinuxDmabufManager;
|
||||
class FourCCStr {
|
||||
public:
|
||||
explicit FourCCStr(uint32_t code)
|
||||
: chars(
|
||||
{static_cast<char>(code >> 0 & 0xff),
|
||||
static_cast<char>(code >> 8 & 0xff),
|
||||
static_cast<char>(code >> 16 & 0xff),
|
||||
static_cast<char>(code >> 24 & 0xff),
|
||||
'\0'}
|
||||
) {
|
||||
for (auto i = 3; i != 0; i--) {
|
||||
if (chars[i] == ' ') chars[i] = '\0';
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* cStr() const { return this->chars.data(); }
|
||||
|
||||
private:
|
||||
std::array<char, 5> chars {};
|
||||
};
|
||||
|
||||
class FourCCModStr {
|
||||
public:
|
||||
explicit FourCCModStr(uint64_t code): drmStr(drmGetFormatModifierName(code)) {}
|
||||
~FourCCModStr() {
|
||||
if (this->drmStr) drmFree(this->drmStr);
|
||||
}
|
||||
|
||||
Q_DISABLE_COPY_MOVE(FourCCModStr);
|
||||
|
||||
[[nodiscard]] const char* cStr() const { return this->drmStr; }
|
||||
|
||||
private:
|
||||
char* drmStr;
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc);
|
||||
QDebug& operator<<(QDebug& debug, const FourCCModStr& fourcc);
|
||||
|
||||
class GbmDeviceHandle {
|
||||
public:
|
||||
GbmDeviceHandle() = default;
|
||||
GbmDeviceHandle(gbm_device* device): device(device) {}
|
||||
|
||||
GbmDeviceHandle(GbmDeviceHandle&& other) noexcept: device(other.device) {
|
||||
other.device = nullptr;
|
||||
}
|
||||
|
||||
~GbmDeviceHandle();
|
||||
Q_DISABLE_COPY(GbmDeviceHandle);
|
||||
|
||||
GbmDeviceHandle& operator=(GbmDeviceHandle&& other) noexcept {
|
||||
this->device = other.device;
|
||||
other.device = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] gbm_device* operator*() const { return this->device; }
|
||||
[[nodiscard]] operator bool() const { return this->device; }
|
||||
|
||||
private:
|
||||
gbm_device* device = nullptr;
|
||||
};
|
||||
|
||||
class WlDmaBufferQSGTexture: public WlBufferQSGTexture {
|
||||
public:
|
||||
~WlDmaBufferQSGTexture() override;
|
||||
Q_DISABLE_COPY_MOVE(WlDmaBufferQSGTexture);
|
||||
|
||||
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; }
|
||||
|
||||
private:
|
||||
WlDmaBufferQSGTexture(EGLImage eglImage, GLuint glTexture, QSGTexture* qsgTexture)
|
||||
: eglImage(eglImage)
|
||||
, glTexture(glTexture)
|
||||
, qsgTexture(qsgTexture) {}
|
||||
|
||||
EGLImage eglImage;
|
||||
GLuint glTexture;
|
||||
QSGTexture* qsgTexture;
|
||||
|
||||
friend class WlDmaBuffer;
|
||||
};
|
||||
|
||||
class WlDmaBuffer: public WlBuffer {
|
||||
public:
|
||||
~WlDmaBuffer() override;
|
||||
Q_DISABLE_COPY(WlDmaBuffer);
|
||||
WlDmaBuffer(WlDmaBuffer&& other) noexcept;
|
||||
WlDmaBuffer& operator=(WlDmaBuffer&& other) noexcept;
|
||||
|
||||
[[nodiscard]] wl_buffer* buffer() const override { return this->mBuffer; }
|
||||
|
||||
[[nodiscard]] QSize size() const override {
|
||||
return QSize(static_cast<int>(this->width), static_cast<int>(this->height));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isCompatible(const WlBufferRequest& request) const override;
|
||||
[[nodiscard]] WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const override;
|
||||
|
||||
private:
|
||||
WlDmaBuffer() noexcept = default;
|
||||
|
||||
struct Plane {
|
||||
int fd = 0;
|
||||
uint32_t offset = 0;
|
||||
uint32_t stride = 0;
|
||||
};
|
||||
|
||||
GbmDeviceHandle device;
|
||||
gbm_bo* bo = nullptr;
|
||||
wl_buffer* mBuffer = nullptr;
|
||||
int planeCount = 0;
|
||||
Plane* planes = nullptr;
|
||||
uint32_t format = 0;
|
||||
uint64_t modifier = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
|
||||
friend class LinuxDmabufManager;
|
||||
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
|
||||
|
||||
struct LinuxDmabufModifiers {
|
||||
StackList<uint64_t, 10> modifiers;
|
||||
bool implicit = false;
|
||||
};
|
||||
|
||||
struct LinuxDmabufFormatSelection {
|
||||
bool sorted = false;
|
||||
StackList<QPair<uint32_t, LinuxDmabufModifiers>, 2> formats;
|
||||
void ensureSorted();
|
||||
};
|
||||
|
||||
struct LinuxDmabufTranche {
|
||||
dev_t device = 0;
|
||||
uint32_t flags = 0;
|
||||
LinuxDmabufFormatSelection formats;
|
||||
};
|
||||
|
||||
class LinuxDmabufFeedback: public QtWayland::zwp_linux_dmabuf_feedback_v1 {
|
||||
public:
|
||||
explicit LinuxDmabufFeedback(::zwp_linux_dmabuf_feedback_v1* feedback);
|
||||
~LinuxDmabufFeedback() override;
|
||||
Q_DISABLE_COPY_MOVE(LinuxDmabufFeedback);
|
||||
|
||||
protected:
|
||||
void zwp_linux_dmabuf_feedback_v1_main_device(wl_array* device) override;
|
||||
void zwp_linux_dmabuf_feedback_v1_format_table(int32_t fd, uint32_t size) override;
|
||||
void zwp_linux_dmabuf_feedback_v1_tranche_target_device(wl_array* device) override;
|
||||
void zwp_linux_dmabuf_feedback_v1_tranche_flags(uint32_t flags) override;
|
||||
void zwp_linux_dmabuf_feedback_v1_tranche_formats(wl_array* indices) override;
|
||||
void zwp_linux_dmabuf_feedback_v1_tranche_done() override;
|
||||
void zwp_linux_dmabuf_feedback_v1_done() override;
|
||||
|
||||
private:
|
||||
dev_t mainDevice = 0;
|
||||
QList<LinuxDmabufTranche> tranches;
|
||||
void* formatTable = nullptr;
|
||||
uint32_t formatTableSize = 0;
|
||||
};
|
||||
|
||||
class LinuxDmabufManager
|
||||
: public QWaylandClientExtensionTemplate<LinuxDmabufManager>
|
||||
, public QtWayland::zwp_linux_dmabuf_v1 {
|
||||
public:
|
||||
explicit LinuxDmabufManager(WlBufferManagerPrivate* manager);
|
||||
|
||||
[[nodiscard]] WlBuffer* createDmabuf(const WlBufferRequest& request);
|
||||
|
||||
[[nodiscard]] WlBuffer* createDmabuf(
|
||||
GbmDeviceHandle& device,
|
||||
uint32_t format,
|
||||
const LinuxDmabufModifiers& modifiers,
|
||||
uint32_t width,
|
||||
uint32_t height
|
||||
);
|
||||
|
||||
private:
|
||||
struct SharedGbmDevice {
|
||||
dev_t handle = 0;
|
||||
std::string renderNode;
|
||||
gbm_device* device = nullptr;
|
||||
qsizetype refcount = 0;
|
||||
};
|
||||
|
||||
void feedbackDone();
|
||||
|
||||
GbmDeviceHandle getGbmDevice(dev_t handle);
|
||||
void unrefGbmDevice(gbm_device* device);
|
||||
GbmDeviceHandle dupHandle(const GbmDeviceHandle& handle);
|
||||
|
||||
QList<LinuxDmabufTranche> tranches;
|
||||
QList<SharedGbmDevice> gbmDevices;
|
||||
WlBufferManagerPrivate* manager;
|
||||
|
||||
friend class LinuxDmabufFeedback;
|
||||
friend class GbmDeviceHandle;
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer::dmabuf
|
137
src/wayland/buffer/manager.cpp
Normal file
137
src/wayland/buffer/manager.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include "manager.hpp"
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qmatrix4x4.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qvectornd.h>
|
||||
|
||||
#include "dmabuf.hpp"
|
||||
#include "manager_p.hpp"
|
||||
#include "qsg.hpp"
|
||||
#include "shm.hpp"
|
||||
|
||||
namespace qs::wayland::buffer {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logBuffer, "quickshell.wayland.buffer", QtWarningMsg);
|
||||
}
|
||||
|
||||
WlBuffer* WlBufferSwapchain::createBackbuffer(const WlBufferRequest& request, bool* newBuffer) {
|
||||
auto& buffer = this->presentSecondBuffer ? this->buffer1 : this->buffer2;
|
||||
|
||||
if (!buffer || !buffer->isCompatible(request)) {
|
||||
buffer.reset(WlBufferManager::instance()->createBuffer(request));
|
||||
if (newBuffer) *newBuffer = true;
|
||||
}
|
||||
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
WlBufferManager::WlBufferManager(): p(new WlBufferManagerPrivate(this)) {}
|
||||
|
||||
WlBufferManager::~WlBufferManager() { delete this->p; }
|
||||
|
||||
WlBufferManager* WlBufferManager::instance() {
|
||||
static auto* instance = new WlBufferManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool WlBufferManager::isReady() const { return this->p->mReady; }
|
||||
|
||||
[[nodiscard]] WlBuffer* WlBufferManager::createBuffer(const WlBufferRequest& request) {
|
||||
static const bool dmabufDisabled = qEnvironmentVariableIsSet("QS_DISABLE_DMABUF");
|
||||
|
||||
qCDebug(logBuffer).nospace() << "Creating buffer from request at " << request.width << 'x'
|
||||
<< request.height;
|
||||
qCDebug(logBuffer).nospace() << " Dmabuf requests on device " << request.dmabuf.device
|
||||
<< " (disabled: " << dmabufDisabled << ')';
|
||||
|
||||
for (const auto& [format, modifiers]: request.dmabuf.formats) {
|
||||
qCDebug(logBuffer) << " Format" << dmabuf::FourCCStr(format);
|
||||
|
||||
for (const auto& modifier: modifiers) {
|
||||
qCDebug(logBuffer) << " Explicit Modifier" << dmabuf::FourCCModStr(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(logBuffer).nospace() << " Shm requests";
|
||||
|
||||
for (const auto& format: request.shm.formats) {
|
||||
qCDebug(logBuffer) << " Format" << format;
|
||||
}
|
||||
|
||||
if (!dmabufDisabled) {
|
||||
if (auto* buf = this->p->dmabuf.createDmabuf(request)) return buf;
|
||||
qCWarning(logBuffer) << "DMA buffer creation failed, falling back to SHM.";
|
||||
}
|
||||
|
||||
return shm::ShmbufManager::createShmbuf(request);
|
||||
}
|
||||
|
||||
WlBufferManagerPrivate::WlBufferManagerPrivate(WlBufferManager* manager)
|
||||
: manager(manager)
|
||||
, dmabuf(this) {}
|
||||
|
||||
void WlBufferManagerPrivate::dmabufReady() {
|
||||
this->mReady = true;
|
||||
emit this->manager->ready();
|
||||
}
|
||||
|
||||
WlBufferQSGDisplayNode::WlBufferQSGDisplayNode(QQuickWindow* window)
|
||||
: window(window)
|
||||
, imageNode(window->createImageNode()) {
|
||||
this->appendChildNode(this->imageNode);
|
||||
}
|
||||
|
||||
void WlBufferQSGDisplayNode::setRect(const QRectF& rect) {
|
||||
const auto* buffer = (this->presentSecondBuffer ? this->buffer2 : this->buffer1).first;
|
||||
if (!buffer) return;
|
||||
|
||||
auto matrix = QMatrix4x4();
|
||||
auto center = rect.center();
|
||||
auto centerX = static_cast<float>(center.x());
|
||||
auto centerY = static_cast<float>(center.y());
|
||||
matrix.translate(centerX, centerY);
|
||||
buffer->transform.apply(matrix);
|
||||
matrix.translate(-centerX, -centerY);
|
||||
|
||||
auto viewRect = matrix.mapRect(rect);
|
||||
auto bufferSize = buffer->size().toSizeF();
|
||||
|
||||
bufferSize.scale(viewRect.width(), viewRect.height(), Qt::KeepAspectRatio);
|
||||
this->imageNode->setRect(
|
||||
viewRect.x() + viewRect.width() / 2 - bufferSize.width() / 2,
|
||||
viewRect.y() + viewRect.height() / 2 - bufferSize.height() / 2,
|
||||
bufferSize.width(),
|
||||
bufferSize.height()
|
||||
);
|
||||
|
||||
this->setMatrix(matrix);
|
||||
}
|
||||
|
||||
void WlBufferQSGDisplayNode::syncSwapchain(const WlBufferSwapchain& swapchain) {
|
||||
auto* buffer = swapchain.frontbuffer();
|
||||
auto& texture = swapchain.presentSecondBuffer ? this->buffer2 : this->buffer1;
|
||||
|
||||
if (swapchain.presentSecondBuffer == this->presentSecondBuffer && texture.first == buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->presentSecondBuffer = swapchain.presentSecondBuffer;
|
||||
|
||||
if (texture.first == buffer) {
|
||||
texture.second->sync(texture.first, this->window);
|
||||
} else {
|
||||
texture.first = buffer;
|
||||
texture.second.reset(buffer->createQsgTexture(this->window));
|
||||
}
|
||||
|
||||
this->imageNode->setTexture(texture.second->texture());
|
||||
}
|
||||
|
||||
} // namespace qs::wayland::buffer
|
134
src/wayland/buffer/manager.hpp
Normal file
134
src/wayland/buffer/manager.hpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include <qhash.h>
|
||||
#include <qlist.h>
|
||||
#include <qmatrix4x4.h>
|
||||
#include <qobject.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qvariant.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "../../core/stacklist.hpp"
|
||||
|
||||
class QQuickWindow;
|
||||
|
||||
namespace qs::wayland::buffer {
|
||||
|
||||
class WlBufferManagerPrivate;
|
||||
class WlBufferQSGTexture;
|
||||
|
||||
struct WlBufferTransform {
|
||||
enum Transform : uint8_t {
|
||||
Normal0 = 0,
|
||||
Normal90 = 1,
|
||||
Normal180 = 2,
|
||||
Normal270 = 3,
|
||||
Flipped0 = 4,
|
||||
Flipped90 = 5,
|
||||
Flipped180 = 6,
|
||||
Flipped270 = 7,
|
||||
} transform = Normal0;
|
||||
|
||||
WlBufferTransform() = default;
|
||||
WlBufferTransform(uint8_t transform): transform(static_cast<Transform>(transform)) {}
|
||||
|
||||
[[nodiscard]] int degrees() const { return 90 * (this->transform & 0b11111011); }
|
||||
[[nodiscard]] bool flip() const { return this->transform & 0b00000100; }
|
||||
|
||||
void apply(QMatrix4x4& matrix) const {
|
||||
matrix.rotate(this->flip() ? 180 : 0, 0, 1, 0);
|
||||
matrix.rotate(static_cast<float>(this->degrees()), 0, 0, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct WlBufferRequest {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
|
||||
struct DmaFormat {
|
||||
DmaFormat() = default;
|
||||
DmaFormat(uint32_t format): format(format) {}
|
||||
|
||||
uint32_t format = 0;
|
||||
StackList<uint64_t, 10> modifiers;
|
||||
};
|
||||
|
||||
struct {
|
||||
StackList<uint32_t, 1> formats;
|
||||
} shm;
|
||||
|
||||
struct {
|
||||
dev_t device = 0;
|
||||
StackList<DmaFormat, 1> formats;
|
||||
} dmabuf;
|
||||
};
|
||||
|
||||
class WlBuffer {
|
||||
public:
|
||||
virtual ~WlBuffer() = default;
|
||||
Q_DISABLE_COPY_MOVE(WlBuffer);
|
||||
|
||||
[[nodiscard]] virtual wl_buffer* buffer() const = 0;
|
||||
[[nodiscard]] virtual QSize size() const = 0;
|
||||
[[nodiscard]] virtual bool isCompatible(const WlBufferRequest& request) const = 0;
|
||||
[[nodiscard]] operator bool() const { return this->buffer(); }
|
||||
|
||||
// Must be called from render thread.
|
||||
[[nodiscard]] virtual WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const = 0;
|
||||
|
||||
WlBufferTransform transform;
|
||||
|
||||
protected:
|
||||
explicit WlBuffer() = default;
|
||||
};
|
||||
|
||||
class WlBufferSwapchain {
|
||||
public:
|
||||
[[nodiscard]] WlBuffer*
|
||||
createBackbuffer(const WlBufferRequest& request, bool* newBuffer = nullptr);
|
||||
|
||||
void swapBuffers() { this->presentSecondBuffer = !this->presentSecondBuffer; }
|
||||
|
||||
[[nodiscard]] WlBuffer* backbuffer() const {
|
||||
return this->presentSecondBuffer ? this->buffer1.get() : this->buffer2.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] WlBuffer* frontbuffer() const {
|
||||
return this->presentSecondBuffer ? this->buffer2.get() : this->buffer1.get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<WlBuffer> buffer1;
|
||||
std::unique_ptr<WlBuffer> buffer2;
|
||||
bool presentSecondBuffer = false;
|
||||
|
||||
friend class WlBufferQSGDisplayNode;
|
||||
};
|
||||
|
||||
class WlBufferManager: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
~WlBufferManager() override;
|
||||
Q_DISABLE_COPY_MOVE(WlBufferManager);
|
||||
|
||||
static WlBufferManager* instance();
|
||||
|
||||
[[nodiscard]] bool isReady() const;
|
||||
[[nodiscard]] WlBuffer* createBuffer(const WlBufferRequest& request);
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
|
||||
private:
|
||||
explicit WlBufferManager();
|
||||
|
||||
WlBufferManagerPrivate* p;
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer
|
20
src/wayland/buffer/manager_p.hpp
Normal file
20
src/wayland/buffer/manager_p.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "dmabuf.hpp"
|
||||
#include "manager.hpp"
|
||||
|
||||
namespace qs::wayland::buffer {
|
||||
|
||||
class WlBufferManagerPrivate {
|
||||
public:
|
||||
explicit WlBufferManagerPrivate(WlBufferManager* manager);
|
||||
|
||||
void dmabufReady();
|
||||
|
||||
WlBufferManager* manager;
|
||||
dmabuf::LinuxDmabufManager dmabuf;
|
||||
|
||||
bool mReady = false;
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer
|
45
src/wayland/buffer/qsg.hpp
Normal file
45
src/wayland/buffer/qsg.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qsgimagenode.h>
|
||||
#include <qsgnode.h>
|
||||
#include <qsgtexture.h>
|
||||
#include <qvectornd.h>
|
||||
|
||||
#include "manager.hpp"
|
||||
|
||||
namespace qs::wayland::buffer {
|
||||
|
||||
// Interact only from QSG thread.
|
||||
class WlBufferQSGTexture {
|
||||
public:
|
||||
virtual ~WlBufferQSGTexture() = default;
|
||||
Q_DISABLE_COPY_MOVE(WlBufferQSGTexture);
|
||||
|
||||
[[nodiscard]] virtual QSGTexture* texture() const = 0;
|
||||
virtual void sync(const WlBuffer* /*buffer*/, QQuickWindow* /*window*/) {}
|
||||
|
||||
protected:
|
||||
WlBufferQSGTexture() = default;
|
||||
};
|
||||
|
||||
// Interact only from QSG thread.
|
||||
class WlBufferQSGDisplayNode: public QSGTransformNode {
|
||||
public:
|
||||
explicit WlBufferQSGDisplayNode(QQuickWindow* window);
|
||||
|
||||
void syncSwapchain(const WlBufferSwapchain& swapchain);
|
||||
void setRect(const QRectF& rect);
|
||||
|
||||
private:
|
||||
QQuickWindow* window;
|
||||
QSGImageNode* imageNode;
|
||||
QPair<WlBuffer*, std::unique_ptr<WlBufferQSGTexture>> buffer1;
|
||||
QPair<WlBuffer*, std::unique_ptr<WlBufferQSGTexture>> buffer2;
|
||||
bool presentSecondBuffer = false;
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer
|
93
src/wayland/buffer/shm.cpp
Normal file
93
src/wayland/buffer/shm.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "shm.hpp"
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include <private/qwaylanddisplay_p.h>
|
||||
#include <private/qwaylandintegration_p.h>
|
||||
#include <private/qwaylandshm_p.h>
|
||||
#include <private/qwaylandshmbackingstore_p.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qsize.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
#include "manager.hpp"
|
||||
|
||||
namespace qs::wayland::buffer::shm {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logShm, "quickshell.wayland.buffer.shm", QtWarningMsg);
|
||||
}
|
||||
|
||||
bool WlShmBuffer::isCompatible(const WlBufferRequest& request) const {
|
||||
if (QSize(static_cast<int>(request.width), static_cast<int>(request.height)) != this->size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto matchingFormat = std::ranges::find(request.shm.formats, this->format);
|
||||
return matchingFormat != request.shm.formats.end();
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
debug.nospace();
|
||||
|
||||
if (buffer) {
|
||||
auto fmt = QtWaylandClient::QWaylandShm::formatFrom(
|
||||
static_cast<::wl_shm_format>(buffer->format) // NOLINT
|
||||
);
|
||||
|
||||
debug << "WlShmBuffer(" << static_cast<const void*>(buffer) << ", size=" << buffer->size()
|
||||
<< ", format=" << fmt << ')';
|
||||
} else {
|
||||
debug << "WlShmBuffer(0x0)";
|
||||
}
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
WlShmBuffer::~WlShmBuffer() { qCDebug(logShm) << "Destroyed" << this; }
|
||||
|
||||
WlBufferQSGTexture* WlShmBuffer::createQsgTexture(QQuickWindow* window) const {
|
||||
auto* texture = new WlShmBufferQSGTexture();
|
||||
|
||||
// If the QWaylandShmBuffer is destroyed before the QSGTexture, we'll hit a UAF
|
||||
// in the render thread.
|
||||
texture->shmBuffer = this->shmBuffer;
|
||||
|
||||
texture->qsgTexture.reset(window->createTextureFromImage(*this->shmBuffer->image()));
|
||||
texture->sync(this, window);
|
||||
return texture;
|
||||
}
|
||||
|
||||
void WlShmBufferQSGTexture::sync(const WlBuffer* /*unused*/, QQuickWindow* window) {
|
||||
// This is both dumb and expensive. We should use an RHI texture and render images into
|
||||
// it more intelligently, but shm buffers are already a horribly slow fallback path,
|
||||
// to the point where it barely matters.
|
||||
this->qsgTexture.reset(window->createTextureFromImage(*this->shmBuffer->image()));
|
||||
}
|
||||
|
||||
WlBuffer* ShmbufManager::createShmbuf(const WlBufferRequest& request) {
|
||||
if (request.shm.formats.isEmpty()) return nullptr;
|
||||
|
||||
static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance();
|
||||
auto* display = waylandIntegration->display();
|
||||
|
||||
// Its probably fine...
|
||||
auto format = request.shm.formats[0];
|
||||
|
||||
auto* buffer = new WlShmBuffer(
|
||||
new QtWaylandClient::QWaylandShmBuffer(
|
||||
display,
|
||||
QSize(static_cast<int>(request.width), static_cast<int>(request.height)),
|
||||
QtWaylandClient::QWaylandShm::formatFrom(static_cast<::wl_shm_format>(format)) // NOLINT
|
||||
),
|
||||
format
|
||||
);
|
||||
|
||||
qCDebug(logShm) << "Created shmbuf" << buffer;
|
||||
return buffer;
|
||||
}
|
||||
} // namespace qs::wayland::buffer::shm
|
61
src/wayland/buffer/shm.hpp
Normal file
61
src/wayland/buffer/shm.hpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <private/qwaylandshmbackingstore_p.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qsgtexture.h>
|
||||
#include <qsize.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
#include "manager.hpp"
|
||||
#include "qsg.hpp"
|
||||
|
||||
namespace qs::wayland::buffer::shm {
|
||||
|
||||
class WlShmBuffer: public WlBuffer {
|
||||
public:
|
||||
~WlShmBuffer() override;
|
||||
Q_DISABLE_COPY_MOVE(WlShmBuffer);
|
||||
|
||||
[[nodiscard]] wl_buffer* buffer() const override { return this->shmBuffer->buffer(); }
|
||||
[[nodiscard]] QSize size() const override { return this->shmBuffer->size(); }
|
||||
[[nodiscard]] bool isCompatible(const WlBufferRequest& request) const override;
|
||||
[[nodiscard]] WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const override;
|
||||
|
||||
private:
|
||||
WlShmBuffer(QtWaylandClient::QWaylandShmBuffer* shmBuffer, uint32_t format)
|
||||
: shmBuffer(shmBuffer)
|
||||
, format(format) {}
|
||||
|
||||
std::shared_ptr<QtWaylandClient::QWaylandShmBuffer> shmBuffer;
|
||||
uint32_t format;
|
||||
|
||||
friend class WlShmBufferQSGTexture;
|
||||
friend class ShmbufManager;
|
||||
friend QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer);
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer);
|
||||
|
||||
class WlShmBufferQSGTexture: public WlBufferQSGTexture {
|
||||
public:
|
||||
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture.get(); }
|
||||
void sync(const WlBuffer* buffer, QQuickWindow* window) override;
|
||||
|
||||
private:
|
||||
WlShmBufferQSGTexture() = default;
|
||||
|
||||
std::shared_ptr<QtWaylandClient::QWaylandShmBuffer> shmBuffer;
|
||||
std::unique_ptr<QSGTexture> qsgTexture;
|
||||
|
||||
friend class WlShmBuffer;
|
||||
};
|
||||
|
||||
class ShmbufManager {
|
||||
public:
|
||||
[[nodiscard]] static WlBuffer* createShmbuf(const WlBufferRequest& request);
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer::shm
|
|
@ -14,13 +14,11 @@ qs_add_module_deps_light(quickshell-hyprland-focus-grab Quickshell)
|
|||
|
||||
install_qml_module(quickshell-hyprland-focus-grab)
|
||||
|
||||
wl_proto(quickshell-hyprland-focus-grab
|
||||
hyprland-focus-grab-v1
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/hyprland-focus-grab-v1.xml"
|
||||
)
|
||||
wl_proto(wlp-hyprland-focus-grab hyprland-focus-grab-v1 "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
target_link_libraries(quickshell-hyprland-focus-grab PRIVATE
|
||||
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
||||
wlp-hyprland-focus-grab
|
||||
)
|
||||
|
||||
qs_module_pch(quickshell-hyprland-focus-grab SET large)
|
||||
|
|
|
@ -12,14 +12,12 @@ qt_add_qml_module(quickshell-hyprland-global-shortcuts
|
|||
|
||||
install_qml_module(quickshell-hyprland-global-shortcuts)
|
||||
|
||||
wl_proto(quickshell-hyprland-global-shortcuts
|
||||
hyprland-global-shortcuts-v1
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/hyprland-global-shortcuts-v1.xml"
|
||||
)
|
||||
wl_proto(wlp-hyprland-shortcuts hyprland-global-shortcuts-v1 "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
target_link_libraries(quickshell-hyprland-global-shortcuts PRIVATE
|
||||
Qt::Qml Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
||||
Qt::Quick # pch
|
||||
wlp-hyprland-shortcuts
|
||||
)
|
||||
|
||||
qs_module_pch(quickshell-hyprland-global-shortcuts)
|
||||
|
|
|
@ -26,8 +26,10 @@
|
|||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logHyprlandIpc, "quickshell.hyprland.ipc", QtWarningMsg);
|
||||
Q_LOGGING_CATEGORY(logHyprlandIpcEvents, "quickshell.hyprland.ipc.events", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
HyprlandIpc::HyprlandIpc() {
|
||||
auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
@ -241,9 +243,8 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
|||
const auto& mList = this->mMonitors.valueList();
|
||||
auto name = QString::fromUtf8(event->data);
|
||||
|
||||
auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
auto monitorIter =
|
||||
std::ranges::find_if(mList, [name](const HyprlandMonitor* m) { return m->name() == name; });
|
||||
|
||||
if (monitorIter == mList.end()) {
|
||||
qCWarning(logHyprlandIpc) << "Got removal for monitor" << name
|
||||
|
@ -292,9 +293,8 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
|||
|
||||
const auto& mList = this->mWorkspaces.valueList();
|
||||
|
||||
auto workspaceIter = std::find_if(mList.begin(), mList.end(), [id](const HyprlandWorkspace* m) {
|
||||
return m->id() == id;
|
||||
});
|
||||
auto workspaceIter =
|
||||
std::ranges::find_if(mList, [id](const HyprlandWorkspace* m) { return m->id() == id; });
|
||||
|
||||
if (workspaceIter == mList.end()) {
|
||||
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << id << "name" << name
|
||||
|
@ -352,6 +352,22 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
|||
auto* monitor = this->findMonitorByName(monitorName, true);
|
||||
|
||||
workspace->setMonitor(monitor);
|
||||
} else if (event->name == "renameworkspace") {
|
||||
auto args = event->parseView(2);
|
||||
auto id = args.at(0).toInt();
|
||||
auto name = QString::fromUtf8(args.at(1));
|
||||
|
||||
const auto& mList = this->mWorkspaces.valueList();
|
||||
|
||||
auto workspaceIter =
|
||||
std::ranges::find_if(mList, [id](const HyprlandWorkspace* m) { return m->id() == id; });
|
||||
|
||||
if (workspaceIter == mList.end()) return;
|
||||
|
||||
qCDebug(logHyprlandIpc) << "Workspace with id" << id << "renamed from"
|
||||
<< (*workspaceIter)->name() << "to" << name;
|
||||
|
||||
(*workspaceIter)->setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,9 +375,8 @@ HyprlandWorkspace*
|
|||
HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
|
||||
const auto& mList = this->mWorkspaces.valueList();
|
||||
|
||||
auto workspaceIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandWorkspace* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
auto workspaceIter =
|
||||
std::ranges::find_if(mList, [name](const HyprlandWorkspace* m) { return m->name() == name; });
|
||||
|
||||
if (workspaceIter != mList.end()) {
|
||||
return *workspaceIter;
|
||||
|
@ -395,10 +410,9 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
|
|||
auto object = entry.toObject().toVariantMap();
|
||||
auto name = object.value("name").toString();
|
||||
|
||||
auto workspaceIter =
|
||||
std::find_if(mList.begin(), mList.end(), [name](const HyprlandWorkspace* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
auto workspaceIter = std::ranges::find_if(mList, [name](const HyprlandWorkspace* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
|
||||
auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
|
||||
auto existed = workspace != nullptr;
|
||||
|
@ -436,9 +450,8 @@ HyprlandMonitor*
|
|||
HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) {
|
||||
const auto& mList = this->mMonitors.valueList();
|
||||
|
||||
auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
auto monitorIter =
|
||||
std::ranges::find_if(mList, [name](const HyprlandMonitor* m) { return m->name() == name; });
|
||||
|
||||
if (monitorIter != mList.end()) {
|
||||
return *monitorIter;
|
||||
|
@ -506,7 +519,7 @@ void HyprlandIpc::refreshMonitors(bool canCreate) {
|
|||
auto object = entry.toObject().toVariantMap();
|
||||
auto name = object.value("name").toString();
|
||||
|
||||
auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) {
|
||||
auto monitorIter = std::ranges::find_if(mList, [name](const HyprlandMonitor* m) {
|
||||
return m->name() == name;
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,15 @@
|
|||
namespace qs::hyprland::ipc {
|
||||
|
||||
qint32 HyprlandWorkspace::id() const { return this->mId; }
|
||||
|
||||
QString HyprlandWorkspace::name() const { return this->mName; }
|
||||
|
||||
void HyprlandWorkspace::setName(QString name) {
|
||||
if (name == this->mName) return;
|
||||
this->mName = std::move(name);
|
||||
emit this->nameChanged();
|
||||
}
|
||||
|
||||
QVariantMap HyprlandWorkspace::lastIpcObject() const { return this->mLastIpcObject; }
|
||||
|
||||
void HyprlandWorkspace::updateInitial(qint32 id, QString name) {
|
||||
|
|
|
@ -32,11 +32,14 @@ public:
|
|||
void updateFromObject(QVariantMap object);
|
||||
|
||||
[[nodiscard]] qint32 id() const;
|
||||
|
||||
[[nodiscard]] QString name() const;
|
||||
void setName(QString name);
|
||||
|
||||
[[nodiscard]] QVariantMap lastIpcObject() const;
|
||||
|
||||
void setMonitor(HyprlandMonitor* monitor);
|
||||
[[nodiscard]] HyprlandMonitor* monitor() const;
|
||||
void setMonitor(HyprlandMonitor* monitor);
|
||||
|
||||
signals:
|
||||
void idChanged();
|
||||
|
|
|
@ -12,13 +12,11 @@ qt_add_qml_module(quickshell-hyprland-surface-extensions
|
|||
|
||||
install_qml_module(quickshell-hyprland-surface-extensions)
|
||||
|
||||
wl_proto(quickshell-hyprland-surface-extensions
|
||||
hyprland-surface-v1
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/hyprland-surface-v1.xml"
|
||||
)
|
||||
wl_proto(wlp-hyprland-surface hyprland-surface-v1 "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
target_link_libraries(quickshell-hyprland-surface-extensions PRIVATE
|
||||
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
||||
wlp-hyprland-surface
|
||||
)
|
||||
|
||||
qs_module_pch(quickshell-hyprland-surface-extensions)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
@ -28,7 +31,6 @@ HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) {
|
|||
}
|
||||
}
|
||||
|
||||
qDebug() << "hlwindow for" << proxyWindow;
|
||||
if (!proxyWindow) return nullptr;
|
||||
return new HyprlandWindow(proxyWindow);
|
||||
}
|
||||
|
@ -41,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()) {
|
||||
|
@ -61,14 +72,76 @@ void HyprlandWindow::setOpacity(qreal opacity) {
|
|||
|
||||
this->mOpacity = opacity;
|
||||
|
||||
if (this->surface) {
|
||||
this->surface->setOpacity(opacity);
|
||||
qs::wayland::util::scheduleCommit(this->mWaylandWindow);
|
||||
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
|
||||
|
@ -87,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();
|
||||
|
||||
|
@ -123,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->mWaylandWindow);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,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();
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue