diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39fab13e..feeb746b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,139 +30,6 @@ If the results look stupid, fix the clang-format file if possible, or disable clang-format in the affected area using `// clang-format off` and `// clang-format on`. -#### Style preferences not caught by clang-format -These are flexible. You can ignore them if it looks or works better to -for one reason or another. - -Use `auto` if the type of a variable can be deduced automatically, instead of -redeclaring the returned value's type. Additionally, auto should be used when a -constructor takes arguments. - -```cpp -auto x = ; // ok -auto x = QString::number(3); // ok -QString x; // ok -QString x = "foo"; // ok -auto x = QString("foo"); // ok - -auto x = QString(); // avoid -QString x(); // avoid -QString x("foo"); // avoid -``` - -Put newlines around logical units of code, and after closing braces. If the -most reasonable logical unit of code takes only a single line, it should be -merged into the next single line logical unit if applicable. -```cpp -// multiple units -auto x = ; // unit 1 -auto y = ; // unit 2 - -auto x = ; // unit 1 -emit this->y(); // unit 2 - -auto x1 = ; // unit 1 -auto x2 = ; // unit 1 -auto x3 = ; // unit 1 - -auto y1 = ; // unit 2 -auto y2 = ; // unit 2 -auto y3 = ; // unit 2 - -// one unit -auto x = ; -if (x...) { - // ... -} - -// if more than one variable needs to be used then add a newline -auto x = ; -auto y = ; - -if (x && y) { - // ... -} -``` - -Class formatting: -```cpp -//! Doc comment summary -/// Doc comment body -class Foo: public QObject { - // The Q_OBJECT macro comes first. Macros are ; terminated. - Q_OBJECT; - QML_ELEMENT; - QML_CLASSINFO(...); - // Properties must stay on a single line or the doc generator won't be able to pick them up - Q_PROPERTY(...); - /// Doc comment - Q_PROPERTY(...); - /// Doc comment - Q_PROPERTY(...); - -public: - // Classes should have explicit constructors if they aren't intended to - // implicitly cast. The constructor can be inline in the header if it has no body. - explicit Foo(QObject* parent = nullptr): QObject(parent) {} - - // Instance functions if applicable. - static Foo* instance(); - - // Member functions unrelated to properties come next - void function(); - void function(); - void function(); - - // Then Q_INVOKABLEs - Q_INVOKABLE function(); - /// Doc comment - Q_INVOKABLE function(); - /// Doc comment - Q_INVOKABLE function(); - - // Then property related functions, in the order (bindable, getter, setter). - // Related functions may be included here as well. Function bodies may be inline - // if they are a single expression. There should be a newline between each - // property's methods. - [[nodiscard]] QBindable bindableFoo() { return &this->bFoo; } - [[nodiscard]] T foo() const { return this->foo; } - void setFoo(); - - [[nodiscard]] T bar() const { return this->foo; } - void setBar(); - -signals: - // Signals that are not property change related go first. - // Property change signals go in property definition order. - void asd(); - void asd2(); - void fooChanged(); - void barChanged(); - -public slots: - // generally Q_INVOKABLEs are preferred to public slots. - void slot(); - -private slots: - // ... - -private: - // statics, then functions, then fields - static const foo BAR; - static void foo(); - - void foo(); - void bar(); - - // property related members are prefixed with `m`. - QString mFoo; - QString bar; - - // Bindables go last and should be prefixed with `b`. - Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged); -}; -``` - ### Linter All contributions should pass the linter. @@ -170,11 +37,11 @@ Note that running the linter requires disabling precompiled headers and including the test codepaths: ```sh $ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON -$ just lint-changed +$ just lint ``` If the linter is complaining about something that you think it should not, -please disable the lint in your MR and explain your reasoning if it isn't obvious. +please disable the lint in your MR and explain your reasoning. ### Tests If you feel like the feature you are working on is very complex or likely to break, @@ -210,7 +77,7 @@ and list of headers to scan for documentation. ### Commits Please structure your commit messages as `scope[!]: commit` where the scope is something like `core` or `service/mpris`. (pick what has been -used historically or what makes sense if new). Add `!` for changes that break +used historically or what makes sense if new.) Add `!` for changes that break existing APIs or functionality. Commit descriptions should contain a summary of the changes if they are not diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index eca7270d..6778e984 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -37,7 +37,6 @@ qt_add_library(quickshell-core STATIC common.cpp iconprovider.cpp scriptmodel.cpp - colorquantizer.cpp ) qt_add_qml_module(quickshell-core diff --git a/src/core/colorquantizer.cpp b/src/core/colorquantizer.cpp deleted file mode 100644 index abe6af8e..00000000 --- a/src/core/colorquantizer.cpp +++ /dev/null @@ -1,230 +0,0 @@ -#include "colorquantizer.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace { -Q_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg); -} - -ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize) - : source(source) - , maxDepth(depth) - , rescaleSize(rescaleSize) { - setAutoDelete(false); -} - -void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCancel) { - if (shouldCancel.loadAcquire() || source->isEmpty()) return; - - colors.clear(); - - auto image = QImage(source->toLocalFile()); - if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) { - image = image.scaled( - static_cast(rescaleSize), - static_cast(rescaleSize), - Qt::KeepAspectRatio, - Qt::SmoothTransformation - ); - } - - if (image.isNull()) { - qCWarning(logColorQuantizer) << "Failed to load image from" << source; - return; - } - - QList pixels; - for (int y = 0; y != image.height(); ++y) { - for (int x = 0; x != image.width(); ++x) { - auto pixel = image.pixel(x, y); - if (qAlpha(pixel) == 0) continue; - - pixels.append(QColor::fromRgb(pixel)); - } - } - - auto startTime = QDateTime::currentDateTime(); - - colors = quantization(pixels, 0); - - auto endTime = QDateTime::currentDateTime(); - auto milliseconds = startTime.msecsTo(endTime); - qCDebug(logColorQuantizer) << "Color Quantization took: " << milliseconds << "ms"; -} - -QList ColorQuantizerOperation::quantization( - QList& rgbValues, - qreal depth, - const QAtomicInteger& shouldCancel -) { - if (shouldCancel.loadAcquire()) return QList(); - - if (depth >= maxDepth || rgbValues.isEmpty()) { - if (rgbValues.isEmpty()) return QList(); - - auto totalR = 0; - auto totalG = 0; - auto totalB = 0; - - for (const auto& color: rgbValues) { - if (shouldCancel.loadAcquire()) return QList(); - - totalR += color.red(); - totalG += color.green(); - totalB += color.blue(); - } - - auto avgColor = QColor( - qRound(totalR / static_cast(rgbValues.size())), - qRound(totalG / static_cast(rgbValues.size())), - qRound(totalB / static_cast(rgbValues.size())) - ); - - return QList() << avgColor; - } - - auto dominantChannel = findBiggestColorRange(rgbValues); - std::ranges::sort(rgbValues, [dominantChannel](const auto& a, const auto& b) { - if (dominantChannel == 'r') return a.red() < b.red(); - else if (dominantChannel == 'g') return a.green() < b.green(); - return a.blue() < b.blue(); - }); - - auto mid = rgbValues.size() / 2; - - auto leftHalf = rgbValues.mid(0, mid); - auto rightHalf = rgbValues.mid(mid); - - QList result; - result.append(quantization(leftHalf, depth + 1)); - result.append(quantization(rightHalf, depth + 1)); - - return result; -} - -char ColorQuantizerOperation::findBiggestColorRange(const QList& rgbValues) { - if (rgbValues.isEmpty()) return 'r'; - - auto rMin = 255; - auto gMin = 255; - auto bMin = 255; - auto rMax = 0; - auto gMax = 0; - auto bMax = 0; - - for (const auto& color: rgbValues) { - rMin = qMin(rMin, color.red()); - gMin = qMin(gMin, color.green()); - bMin = qMin(bMin, color.blue()); - - rMax = qMax(rMax, color.red()); - gMax = qMax(gMax, color.green()); - bMax = qMax(bMax, color.blue()); - } - - auto rRange = rMax - rMin; - auto gRange = gMax - gMin; - auto bRange = bMax - bMin; - - auto biggestRange = qMax(rRange, qMax(gRange, bRange)); - if (biggestRange == rRange) { - return 'r'; - } else if (biggestRange == gRange) { - return 'g'; - } else { - return 'b'; - } -} - -void ColorQuantizerOperation::finishRun() { - QMetaObject::invokeMethod(this, &ColorQuantizerOperation::finished, Qt::QueuedConnection); -} - -void ColorQuantizerOperation::finished() { - emit this->done(colors); - delete this; -} - -void ColorQuantizerOperation::run() { - if (!this->shouldCancel) { - this->quantizeImage(); - - if (this->shouldCancel.loadAcquire()) { - qCDebug(logColorQuantizer) << "Color quantization" << this << "cancelled"; - } - } - - this->finishRun(); -} - -void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); } - -void ColorQuantizer::componentComplete() { - componentCompleted = true; - if (!mSource.isEmpty()) quantizeAsync(); -} - -void ColorQuantizer::setSource(const QUrl& source) { - if (mSource != source) { - mSource = source; - emit this->sourceChanged(); - - if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync(); - } -} - -void ColorQuantizer::setDepth(qreal depth) { - if (mDepth != depth) { - mDepth = depth; - emit this->depthChanged(); - - if (this->componentCompleted) quantizeAsync(); - } -} - -void ColorQuantizer::setRescaleSize(int rescaleSize) { - if (mRescaleSize != rescaleSize) { - mRescaleSize = rescaleSize; - emit this->rescaleSizeChanged(); - - if (this->componentCompleted) quantizeAsync(); - } -} - -void ColorQuantizer::operationFinished(const QList& result) { - bColors = result; - this->liveOperation = nullptr; - emit this->colorsChanged(); -} - -void ColorQuantizer::quantizeAsync() { - if (this->liveOperation) this->cancelAsync(); - - qCDebug(logColorQuantizer) << "Starting color quantization asynchronously"; - this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize); - - QObject::connect( - this->liveOperation, - &ColorQuantizerOperation::done, - this, - &ColorQuantizer::operationFinished - ); - - QThreadPool::globalInstance()->start(this->liveOperation); -} - -void ColorQuantizer::cancelAsync() { - if (!this->liveOperation) return; - - this->liveOperation->tryCancel(); - QThreadPool::globalInstance()->waitForDone(); - - QObject::disconnect(this->liveOperation, nullptr, this, nullptr); - this->liveOperation = nullptr; -} diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp deleted file mode 100644 index b8456db4..00000000 --- a/src/core/colorquantizer.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -class ColorQuantizerOperation - : public QObject - , public QRunnable { - Q_OBJECT; - -public: - explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize); - - void run() override; - void tryCancel(); - -signals: - void done(QList colors); - -private slots: - void finished(); - -private: - static char findBiggestColorRange(const QList& rgbValues); - - void quantizeImage(const QAtomicInteger& shouldCancel = false); - - QList quantization( - QList& rgbValues, - qreal depth, - const QAtomicInteger& shouldCancel = false - ); - - void finishRun(); - - QAtomicInteger shouldCancel = false; - QList colors; - QUrl* source; - qreal maxDepth; - qreal rescaleSize; -}; - -///! Color Quantization Utility -/// A color quantization utility used for getting prevalent colors in an image, by -/// averaging out the image's color data recursively. -/// -/// #### Example -/// ```qml -/// ColorQuantizer { -/// id: colorQuantizer -/// source: Qt.resolvedUrl("./yourImage.png") -/// depth: 3 // Will produce 8 colors (2³) -/// rescaleSize: 64 // Rescale to 64x64 for faster processing -/// } -/// ``` -class ColorQuantizer - : public QObject - , public QQmlParserStatus { - - Q_OBJECT; - QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); - /// Access the colors resulting from the color quantization performed. - /// > [!NOTE] The amount of colors returned from the quantization is determined by - /// > the property depth, specifically 2ⁿ where n is the depth. - Q_PROPERTY(QList colors READ default NOTIFY colorsChanged BINDABLE bindableColors); - - /// Path to the image you'd like to run the color quantization on. - Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged); - - /// Max depth for the color quantization. Each level of depth represents another - /// binary split of the color space - Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged); - - /// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done. - /// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's - /// > reccommended to rescale, otherwise the quantization process will take much longer. - Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged); - -public: - explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {} - - void componentComplete() override; - void classBegin() override {} - - [[nodiscard]] QBindable> bindableColors() { return &this->bColors; } - - [[nodiscard]] QUrl source() const { return mSource; } - void setSource(const QUrl& source); - - [[nodiscard]] qreal depth() const { return mDepth; } - void setDepth(qreal depth); - - [[nodiscard]] qreal rescaleSize() const { return mRescaleSize; } - void setRescaleSize(int rescaleSize); - -signals: - void colorsChanged(); - void sourceChanged(); - void depthChanged(); - void rescaleSizeChanged(); - -public slots: - void operationFinished(const QList& result); - -private: - void quantizeAsync(); - void cancelAsync(); - - bool componentCompleted = false; - ColorQuantizerOperation* liveOperation = nullptr; - QUrl mSource; - qreal mDepth = 0; - qreal mRescaleSize = 0; - - Q_OBJECT_BINDABLE_PROPERTY( - ColorQuantizer, - QList, - bColors, - &ColorQuantizer::colorsChanged - ); -}; diff --git a/src/core/module.md b/src/core/module.md index b9404ea9..c8b17ab9 100644 --- a/src/core/module.md +++ b/src/core/module.md @@ -29,6 +29,5 @@ headers = [ "qsmenuanchor.hpp", "clock.hpp", "scriptmodel.hpp", - "colorquantizer.hpp", ] ----- diff --git a/src/wayland/hyprland/ipc/connection.cpp b/src/wayland/hyprland/ipc/connection.cpp index c797b609..794ecff6 100644 --- a/src/wayland/hyprland/ipc/connection.cpp +++ b/src/wayland/hyprland/ipc/connection.cpp @@ -333,7 +333,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { auto* monitor = this->findMonitorByName(name, true); this->setFocusedMonitor(monitor); monitor->setActiveWorkspace(workspace); - qCDebug(logHyprlandIpc) << "Monitor" << name << "focused with workspace" << workspace->id(); } else if (event->name == "workspacev2") { auto args = event->parseView(2); auto id = args.at(0).toInt(); @@ -342,8 +341,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { if (this->mFocusedMonitor != nullptr) { auto* workspace = this->findWorkspaceByName(name, true, id); this->mFocusedMonitor->setActiveWorkspace(workspace); - qCDebug(logHyprlandIpc) << "Workspace" << id << "activated on" - << this->mFocusedMonitor->name(); } } else if (event->name == "moveworkspacev2") { auto args = event->parseView(3); @@ -354,7 +351,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { auto* workspace = this->findWorkspaceByName(name, true, id); auto* monitor = this->findMonitorByName(monitorName, true); - qCDebug(logHyprlandIpc) << "Workspace" << id << "moved to monitor" << monitorName; workspace->setMonitor(monitor); } else if (event->name == "renameworkspace") { auto args = event->parseView(2); @@ -378,28 +374,15 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { HyprlandWorkspace* HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) { const auto& mList = this->mWorkspaces.valueList(); - HyprlandWorkspace* workspace = nullptr; - if (id != -1) { - auto workspaceIter = - std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { return m->id() == id; }); + auto workspaceIter = + std::ranges::find_if(mList, [name](const HyprlandWorkspace* m) { return m->name() == name; }); - workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; - } - - if (!workspace) { - auto workspaceIter = - std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { return m->name() == name; }); - - workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; - } - - if (workspace) { - return workspace; + if (workspaceIter != mList.end()) { + return *workspaceIter; } else if (createIfMissing) { qCDebug(logHyprlandIpc) << "Workspace" << name - << "requested before creation, performing early init with id" << id; - + << "requested before creation, performing early init"; auto* workspace = new HyprlandWorkspace(this); workspace->updateInitial(id, name); this->mWorkspaces.insertObject(workspace); @@ -417,34 +400,24 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) { this->requestingWorkspaces = false; if (!success) return; - qCDebug(logHyprlandIpc) << "Parsing workspaces response"; + qCDebug(logHyprlandIpc) << "parsing workspaces response"; auto json = QJsonDocument::fromJson(resp).array(); const auto& mList = this->mWorkspaces.valueList(); - auto ids = QVector(); + auto names = QVector(); for (auto entry: json) { auto object = entry.toObject().toVariantMap(); + auto name = object.value("name").toString(); - auto id = object.value("id").toInt(); - - auto workspaceIter = - std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { return m->id() == id; }); - - // Only fall back to name-based filtering as a last resort, for workspaces where - // no ID has been determined yet. - if (workspaceIter == mList.end()) { - auto name = object.value("name").toString(); - - workspaceIter = std::ranges::find_if(mList, [&](const HyprlandWorkspace* m) { - return m->id() == -1 && 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; - if (!existed) { + if (workspace == nullptr) { if (!canCreate) continue; workspace = new HyprlandWorkspace(this); } @@ -455,22 +428,20 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) { this->mWorkspaces.insertObject(workspace); } - ids.push_back(id); + names.push_back(name); } - if (canCreate) { - auto removedWorkspaces = QVector(); + auto removedWorkspaces = QVector(); - for (auto* workspace: mList) { - if (!ids.contains(workspace->id())) { - removedWorkspaces.push_back(workspace); - } + for (auto* workspace: mList) { + if (!names.contains(workspace->name())) { + removedWorkspaces.push_back(workspace); } + } - for (auto* workspace: removedWorkspaces) { - this->mWorkspaces.removeObject(workspace); - delete workspace; - } + for (auto* workspace: removedWorkspaces) { + this->mWorkspaces.removeObject(workspace); + delete workspace; } }); } diff --git a/src/wayland/hyprland/ipc/connection.hpp b/src/wayland/hyprland/ipc/connection.hpp index 287b1ee8..856d4173 100644 --- a/src/wayland/hyprland/ipc/connection.hpp +++ b/src/wayland/hyprland/ipc/connection.hpp @@ -81,7 +81,7 @@ public: [[nodiscard]] ObjectModel* workspaces(); // No byId because these preemptively create objects. The given id is set if created. - HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = -1); + HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = 0); HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1); // canCreate avoids making ghost workspaces when the connection races diff --git a/src/wayland/hyprland/ipc/monitor.cpp b/src/wayland/hyprland/ipc/monitor.cpp index 190ab668..8ee5e207 100644 --- a/src/wayland/hyprland/ipc/monitor.cpp +++ b/src/wayland/hyprland/ipc/monitor.cpp @@ -117,8 +117,6 @@ void HyprlandMonitor::setActiveWorkspace(HyprlandWorkspace* workspace) { this->mActiveWorkspace = workspace; if (workspace != nullptr) { - workspace->setMonitor(this); - QObject::connect( workspace, &QObject::destroyed, diff --git a/src/wayland/hyprland/ipc/workspace.cpp b/src/wayland/hyprland/ipc/workspace.cpp index 428edd6b..153dea6b 100644 --- a/src/wayland/hyprland/ipc/workspace.cpp +++ b/src/wayland/hyprland/ipc/workspace.cpp @@ -35,22 +35,18 @@ void HyprlandWorkspace::updateInitial(qint32 id, QString name) { } void HyprlandWorkspace::updateFromObject(QVariantMap object) { + auto id = object.value("id").value(); auto name = object.value("name").value(); auto monitorId = object.value("monitorID").value(); auto monitorName = object.value("monitor").value(); - auto initial = this->mId = -1; - - // ID cannot be updated after creation - if (initial) { - this->mId = object.value("id").value(); + if (id != this->mId) { + this->mId = id; emit this->idChanged(); } - // No events we currently handle give a workspace id but not a name, - // so we shouldn't set this if it isn't an initial query - if (initial && name != this->mName) { - this->mName = name; + if (name != this->mName) { + this->mName = std::move(name); emit this->nameChanged(); }