From 13e8736f32d430e4cd147c8844c336c3cbf586f9 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:47:38 +1000 Subject: [PATCH 1/6] core/qmlglobal: add saveToFile --- src/core/qmlglobal.cpp | 22 ++++++++++++++++++++++ src/core/qmlglobal.hpp | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index 07238f61..9d86337f 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -15,10 +15,13 @@ #include #include #include +#include #include #include +#include #include #include +#include #include #include #include @@ -313,6 +316,25 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback) return IconImageProvider::requestString(icon, "", fallback); } +void QuickshellGlobal::saveToFile( + const QQuickItemGrabResult* grabResult, + const QUrl& filePath +) { + if (!filePath.isLocalFile()) { + qWarning() << "saveToFile can only save to a file on the local filesystem"; + return; + } + + const QString localFile = filePath.toLocalFile(); + QImage image = grabResult->image(); + + QThreadPool::globalInstance()->start([image, localFile] { + if (!image.save(localFile)) { + qWarning() << "Failed to save image to" << localFile; + } + }); +} + QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) { auto* qsg = new QuickshellGlobal(); auto* generation = EngineGeneration::findEngineGeneration(engine); diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index 9d88591e..614685dd 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -184,6 +185,15 @@ class QuickshellGlobal: public QObject { /// This function is equivalent to @@Quickshell.Io.Process.startDetached(). Q_INVOKABLE static void execDetached(const qs::io::process::ProcessContext& context); + /// Save the contents of an ItemGrabResult to a file asynchronously. + /// + /// > [!INFO] This function is equivalent to an asynchronous version of + /// > @@QtQuick.ItemGrabResult.saveToFile(). + /// > + /// > You can use it in a similar way: `Item.grabToImage(res => Quickshell.saveToFile(res, path))` + Q_INVOKABLE static void + saveToFile(const QQuickItemGrabResult* grabResult, const QUrl& filePath); + /// Returns a string usable for a @@QtQuick.Image.source for a given system icon. /// /// > [!INFO] By default, icons are loaded from the theme selected by the qt platform theme, From 3f506f751219750f757e3def02c5b3db0ef7fee8 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:46:14 +1000 Subject: [PATCH 2/6] core/itemimagegrab: create Create ItemImageGrab qml component --- src/core/CMakeLists.txt | 1 + src/core/itemimagegrab.cpp | 43 ++++++++++++++++++++++++++++++++++++++ src/core/itemimagegrab.hpp | 33 +++++++++++++++++++++++++++++ src/core/module.md | 1 + 4 files changed, 78 insertions(+) create mode 100644 src/core/itemimagegrab.cpp create mode 100644 src/core/itemimagegrab.hpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7cef987a..5b77edcf 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -39,6 +39,7 @@ qt_add_library(quickshell-core STATIC scriptmodel.cpp colorquantizer.cpp toolsupport.cpp + itemimagegrab.cpp ) qt_add_qml_module(quickshell-core diff --git a/src/core/itemimagegrab.cpp b/src/core/itemimagegrab.cpp new file mode 100644 index 00000000..dc6a35db --- /dev/null +++ b/src/core/itemimagegrab.cpp @@ -0,0 +1,43 @@ +#include "itemimagegrab.hpp" + +#include +#include +#include +#include +#include +#include + +QQuickItem* ItemImageGrab::target() const { return this->mTarget; } + +void ItemImageGrab::setTarget(QQuickItem* target) { + if (target == this->mTarget) return; + + this->mTarget = target; + emit this->targetChanged(); +} + +void ItemImageGrab::grab(const QUrl& path) { this->grab(this->mTarget, path); } + +void ItemImageGrab::grab(const QUrl& path, const QSize& targetSize) { this->grab(this->mTarget, path, targetSize); } + +void ItemImageGrab::grab(QQuickItem* target, const QUrl& path) { this->grab(target, path, QSize()); } + +void ItemImageGrab::grab(QQuickItem* target, const QUrl& path, const QSize& targetSize) { + if (!target) return; + + QSharedPointer grabResult; + if (targetSize.isEmpty()){ + grabResult = target->grabToImage(); + }else{ + grabResult = target->grabToImage(targetSize);} + + QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, path, this]() { + QThreadPool::globalInstance()->start([grabResult, path, this] { + if (grabResult->saveToFile(path)) { + emit this->saved(path); + } else { + emit this->failed(path); + } + }); + }); +} diff --git a/src/core/itemimagegrab.hpp b/src/core/itemimagegrab.hpp new file mode 100644 index 00000000..a1e54116 --- /dev/null +++ b/src/core/itemimagegrab.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +/// Allows for saving an item grab to an file asynchronously. +class ItemImageGrab: public QObject { + Q_OBJECT; + + Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged); + QML_ELEMENT; + +public: + explicit ItemImageGrab(QObject* parent = nullptr): QObject(parent) {}; + + [[nodiscard]] QQuickItem* target() const; + void setTarget(QQuickItem* target); + + Q_INVOKABLE void grab(const QUrl& path); + Q_INVOKABLE void grab(const QUrl& path, const QSize& targetSize); + Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path); + Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path, const QSize& targetSize); + +signals: + void saved(const QUrl& path); + void failed(const QUrl& path); + + void targetChanged(); + +private: + QPointer mTarget; +}; diff --git a/src/core/module.md b/src/core/module.md index b9404ea9..541733c1 100644 --- a/src/core/module.md +++ b/src/core/module.md @@ -30,5 +30,6 @@ headers = [ "clock.hpp", "scriptmodel.hpp", "colorquantizer.hpp", + "itemimagegrab.hpp", ] ----- From 8507d4aa80516600732efd47f4449b21b0b38794 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:48:42 +1000 Subject: [PATCH 3/6] Revert "core/qmlglobal: add saveToFile" This reverts commit 13e8736f32d430e4cd147c8844c336c3cbf586f9. --- src/core/qmlglobal.cpp | 22 ---------------------- src/core/qmlglobal.hpp | 10 ---------- 2 files changed, 32 deletions(-) diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index 9d86337f..07238f61 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -15,13 +15,10 @@ #include #include #include -#include #include #include -#include #include #include -#include #include #include #include @@ -316,25 +313,6 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback) return IconImageProvider::requestString(icon, "", fallback); } -void QuickshellGlobal::saveToFile( - const QQuickItemGrabResult* grabResult, - const QUrl& filePath -) { - if (!filePath.isLocalFile()) { - qWarning() << "saveToFile can only save to a file on the local filesystem"; - return; - } - - const QString localFile = filePath.toLocalFile(); - QImage image = grabResult->image(); - - QThreadPool::globalInstance()->start([image, localFile] { - if (!image.save(localFile)) { - qWarning() << "Failed to save image to" << localFile; - } - }); -} - QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) { auto* qsg = new QuickshellGlobal(); auto* generation = EngineGeneration::findEngineGeneration(engine); diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index 614685dd..9d88591e 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -185,15 +184,6 @@ class QuickshellGlobal: public QObject { /// This function is equivalent to @@Quickshell.Io.Process.startDetached(). Q_INVOKABLE static void execDetached(const qs::io::process::ProcessContext& context); - /// Save the contents of an ItemGrabResult to a file asynchronously. - /// - /// > [!INFO] This function is equivalent to an asynchronous version of - /// > @@QtQuick.ItemGrabResult.saveToFile(). - /// > - /// > You can use it in a similar way: `Item.grabToImage(res => Quickshell.saveToFile(res, path))` - Q_INVOKABLE static void - saveToFile(const QQuickItemGrabResult* grabResult, const QUrl& filePath); - /// Returns a string usable for a @@QtQuick.Image.source for a given system icon. /// /// > [!INFO] By default, icons are loaded from the theme selected by the qt platform theme, From 3f34303287f76f9315814d7e7622ef59aac651ae Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:34:20 +1000 Subject: [PATCH 4/6] core/itemimagegrab: remove target prop Also format --- src/core/itemimagegrab.cpp | 58 ++++++++++++++++++-------------------- src/core/itemimagegrab.hpp | 22 ++++----------- 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/src/core/itemimagegrab.cpp b/src/core/itemimagegrab.cpp index dc6a35db..84847e90 100644 --- a/src/core/itemimagegrab.cpp +++ b/src/core/itemimagegrab.cpp @@ -7,37 +7,35 @@ #include #include -QQuickItem* ItemImageGrab::target() const { return this->mTarget; } - -void ItemImageGrab::setTarget(QQuickItem* target) { - if (target == this->mTarget) return; - - this->mTarget = target; - emit this->targetChanged(); +void ItemImageGrab::grab(QQuickItem* target, const QUrl& path) { + this->grab(target, path, QSize()); } -void ItemImageGrab::grab(const QUrl& path) { this->grab(this->mTarget, path); } - -void ItemImageGrab::grab(const QUrl& path, const QSize& targetSize) { this->grab(this->mTarget, path, targetSize); } - -void ItemImageGrab::grab(QQuickItem* target, const QUrl& path) { this->grab(target, path, QSize()); } - void ItemImageGrab::grab(QQuickItem* target, const QUrl& path, const QSize& targetSize) { - if (!target) return; - - QSharedPointer grabResult; - if (targetSize.isEmpty()){ - grabResult = target->grabToImage(); - }else{ - grabResult = target->grabToImage(targetSize);} - - QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, path, this]() { - QThreadPool::globalInstance()->start([grabResult, path, this] { - if (grabResult->saveToFile(path)) { - emit this->saved(path); - } else { - emit this->failed(path); - } - }); - }); + if (!target) { + qWarning() << "ItemImageGrab::grab: a target is required"; + return; + } + + QSharedPointer grabResult; + if (targetSize.isEmpty()) { + grabResult = target->grabToImage(); + } else { + grabResult = target->grabToImage(targetSize); + } + + QObject::connect( + grabResult.data(), + &QQuickItemGrabResult::ready, + this, + [grabResult, path, this]() { + QThreadPool::globalInstance()->start([grabResult, path, this] { + if (grabResult->saveToFile(path)) { + emit this->saved(path); + } else { + emit this->failed(path); + } + }); + } + ); } diff --git a/src/core/itemimagegrab.hpp b/src/core/itemimagegrab.hpp index a1e54116..a9df5e46 100644 --- a/src/core/itemimagegrab.hpp +++ b/src/core/itemimagegrab.hpp @@ -7,27 +7,15 @@ /// Allows for saving an item grab to an file asynchronously. class ItemImageGrab: public QObject { Q_OBJECT; - - Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged); - QML_ELEMENT; + QML_ELEMENT; public: explicit ItemImageGrab(QObject* parent = nullptr): QObject(parent) {}; - [[nodiscard]] QQuickItem* target() const; - void setTarget(QQuickItem* target); - - Q_INVOKABLE void grab(const QUrl& path); - Q_INVOKABLE void grab(const QUrl& path, const QSize& targetSize); - Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path); - Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path, const QSize& targetSize); + Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path); + Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path, const QSize& targetSize); signals: - void saved(const QUrl& path); - void failed(const QUrl& path); - - void targetChanged(); - -private: - QPointer mTarget; + void saved(const QUrl& path); + void failed(const QUrl& path); }; From 75312660ab89c83fd0efe3e51f2ad66c19fdc2bb Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:03:44 +1000 Subject: [PATCH 5/6] core:itemimagegrab: add crop and grab method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quickshot coming soon™? --- src/core/itemimagegrab.cpp | 36 +++++++++++++++++++++++++++++++----- src/core/itemimagegrab.hpp | 6 +++++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/core/itemimagegrab.cpp b/src/core/itemimagegrab.cpp index 84847e90..e894ec7b 100644 --- a/src/core/itemimagegrab.cpp +++ b/src/core/itemimagegrab.cpp @@ -12,8 +12,26 @@ void ItemImageGrab::grab(QQuickItem* target, const QUrl& path) { } void ItemImageGrab::grab(QQuickItem* target, const QUrl& path, const QSize& targetSize) { + this->cropAndGrab(target, path, QRect(), targetSize); +} + +void ItemImageGrab::cropAndGrab(QQuickItem* target, const QUrl& path, const QRect& rect) { + this->cropAndGrab(target, path, rect, QSize()); +} + +void ItemImageGrab::cropAndGrab( + QQuickItem* target, + const QUrl& path, + const QRect& rect, + const QSize& targetSize +) { if (!target) { - qWarning() << "ItemImageGrab::grab: a target is required"; + qWarning() << "ItemImageGrab: a target is required"; + return; + } + + if (!path.isLocalFile()) { + qWarning() << "ItemImageGrab: can only save to a file on the local filesystem"; return; } @@ -28,10 +46,18 @@ void ItemImageGrab::grab(QQuickItem* target, const QUrl& path, const QSize& targ grabResult.data(), &QQuickItemGrabResult::ready, this, - [grabResult, path, this]() { - QThreadPool::globalInstance()->start([grabResult, path, this] { - if (grabResult->saveToFile(path)) { - emit this->saved(path); + [grabResult, rect, path, this]() { + QThreadPool::globalInstance()->start([grabResult, rect, path, this] { + QImage image = grabResult->image(); + + if (!rect.isEmpty()) { + image = image.copy(rect); + } + + const QString localFile = path.toLocalFile(); + + if (image.save(localFile)) { + emit this->saved(localFile); } else { emit this->failed(path); } diff --git a/src/core/itemimagegrab.hpp b/src/core/itemimagegrab.hpp index a9df5e46..b35ba379 100644 --- a/src/core/itemimagegrab.hpp +++ b/src/core/itemimagegrab.hpp @@ -15,7 +15,11 @@ class ItemImageGrab: public QObject { Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path); Q_INVOKABLE void grab(QQuickItem* target, const QUrl& path, const QSize& targetSize); + Q_INVOKABLE void cropAndGrab(QQuickItem* target, const QUrl& path, const QRect& rect); + Q_INVOKABLE void + cropAndGrab(QQuickItem* target, const QUrl& path, const QRect& rect, const QSize& targetSize); + signals: - void saved(const QUrl& path); + void saved(const QString& path); void failed(const QUrl& path); }; From 530628694f519b5ae8b3c877c21ad36ef2a9668f Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:48:57 +1000 Subject: [PATCH 6/6] core/itemimagegrab: add path to saved signal --- src/core/itemimagegrab.cpp | 2 +- src/core/itemimagegrab.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/itemimagegrab.cpp b/src/core/itemimagegrab.cpp index e894ec7b..06ee7b7e 100644 --- a/src/core/itemimagegrab.cpp +++ b/src/core/itemimagegrab.cpp @@ -57,7 +57,7 @@ void ItemImageGrab::cropAndGrab( const QString localFile = path.toLocalFile(); if (image.save(localFile)) { - emit this->saved(localFile); + emit this->saved(localFile, path); } else { emit this->failed(path); } diff --git a/src/core/itemimagegrab.hpp b/src/core/itemimagegrab.hpp index b35ba379..619c2fc7 100644 --- a/src/core/itemimagegrab.hpp +++ b/src/core/itemimagegrab.hpp @@ -20,6 +20,6 @@ class ItemImageGrab: public QObject { cropAndGrab(QQuickItem* target, const QUrl& path, const QRect& rect, const QSize& targetSize); signals: - void saved(const QString& path); + void saved(const QString& file, const QUrl& path); void failed(const QUrl& path); };