summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/controls/IconButton.qml68
-rw-r--r--components/controls/StyledScrollBar.qml14
-rw-r--r--config/GeneralConfig.qml2
-rw-r--r--modules/utilities/Content.qml6
-rw-r--r--modules/utilities/Wrapper.qml7
-rw-r--r--modules/utilities/cards/IdleInhibit.qml5
-rw-r--r--modules/utilities/cards/Record.qml315
-rw-r--r--modules/utilities/cards/Toggles.qml2
-rw-r--r--plugin/src/Caelestia/Models/filesystemmodel.cpp67
-rw-r--r--plugin/src/Caelestia/Models/filesystemmodel.hpp16
-rw-r--r--plugin/src/Caelestia/cutils.cpp13
-rw-r--r--plugin/src/Caelestia/cutils.hpp4
-rw-r--r--services/Recorder.qml38
-rw-r--r--utils/Paths.qml2
14 files changed, 536 insertions, 23 deletions
diff --git a/components/controls/IconButton.qml b/components/controls/IconButton.qml
new file mode 100644
index 0000000..7eadcc3
--- /dev/null
+++ b/components/controls/IconButton.qml
@@ -0,0 +1,68 @@
+import ".."
+import qs.services
+import qs.config
+import QtQuick
+
+StyledRect {
+ id: root
+
+ enum Type {
+ Filled,
+ Tonal,
+ Text
+ }
+
+ required property string icon
+ property bool checked
+ property bool toggle
+ property real padding: type == IconButton.Text ? Appearance.padding.small / 2 : Appearance.padding.smaller
+ property alias font: label.font
+ property int type: IconButton.Filled
+
+ property alias label: label
+
+ property bool internalChecked
+ property color activeColour: type == IconButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
+ property color inactiveColour: type == IconButton.Filled ? Colours.palette.m3surfaceContainer : Colours.palette.m3secondaryContainer
+ property color activeOnColour: type == IconButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
+ property color inactiveOnColour: type == IconButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
+
+ function onClicked(): void {
+ }
+
+ onCheckedChanged: internalChecked = checked
+
+ radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
+ color: type == IconButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour
+
+ implicitWidth: implicitHeight
+ implicitHeight: label.implicitHeight + padding * 2
+
+ StateLayer {
+ color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
+
+ function onClicked(): void {
+ if (root.toggle)
+ root.internalChecked = !root.internalChecked;
+ root.onClicked();
+ }
+ }
+
+ MaterialIcon {
+ id: label
+
+ anchors.centerIn: parent
+
+ text: root.icon
+ color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
+ fill: root.internalChecked ? 1 : 0
+
+ Behavior on fill {
+ Anim {}
+ }
+ }
+
+ Behavior on radius {
+ Anim {}
+ }
+}
diff --git a/components/controls/StyledScrollBar.qml b/components/controls/StyledScrollBar.qml
index 69a01f3..ea895ae 100644
--- a/components/controls/StyledScrollBar.qml
+++ b/components/controls/StyledScrollBar.qml
@@ -7,12 +7,24 @@ import QtQuick.Controls
ScrollBar {
id: root
+ leftPadding: 0
+
contentItem: StyledRect {
+ x: 0
implicitWidth: 6
- opacity: root.pressed ? 1 : root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1) ? 0.8 : 0
+ opacity: root.pressed ? 1 : mouse.containsMouse ? 0.8 : root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1) ? 0.6 : 0
radius: Appearance.rounding.full
color: Colours.palette.m3secondary
+ MouseArea {
+ id: mouse
+
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ hoverEnabled: true
+ acceptedButtons: Qt.NoButton
+ }
+
Behavior on opacity {
Anim {}
}
diff --git a/config/GeneralConfig.qml b/config/GeneralConfig.qml
index 4e2a5fb..b9fa364 100644
--- a/config/GeneralConfig.qml
+++ b/config/GeneralConfig.qml
@@ -6,5 +6,7 @@ JsonObject {
component Apps: JsonObject {
property list<string> terminal: ["foot"]
property list<string> audio: ["pavucontrol"]
+ property list<string> playback: ["mpv"]
+ property list<string> explorer: ["thunar"]
}
}
diff --git a/modules/utilities/Content.qml b/modules/utilities/Content.qml
index bcac9b8..3e29402 100644
--- a/modules/utilities/Content.qml
+++ b/modules/utilities/Content.qml
@@ -5,12 +5,18 @@ import QtQuick.Layouts
ColumnLayout {
id: root
+ required property var props
required property var visibilities
spacing: Appearance.spacing.normal
IdleInhibit {}
+ Record {
+ props: root.props
+ visibilities: root.visibilities
+ }
+
Toggles {
visibilities: root.visibilities
}
diff --git a/modules/utilities/Wrapper.qml b/modules/utilities/Wrapper.qml
index 830ee5a..4de4014 100644
--- a/modules/utilities/Wrapper.qml
+++ b/modules/utilities/Wrapper.qml
@@ -2,12 +2,18 @@ pragma ComponentBehavior: Bound
import qs.components
import qs.config
+import Quickshell
import QtQuick
Item {
id: root
required property var visibilities
+ readonly property PersistentProperties props: PersistentProperties {
+ property bool recordingListExpanded: false
+
+ reloadableId: "utilities"
+ }
visible: height > 0
implicitHeight: 0
@@ -76,6 +82,7 @@ Item {
active: true
sourceComponent: Content {
+ props: root.props
visibilities: root.visibilities
}
}
diff --git a/modules/utilities/cards/IdleInhibit.qml b/modules/utilities/cards/IdleInhibit.qml
index 87f6211..4e95a56 100644
--- a/modules/utilities/cards/IdleInhibit.qml
+++ b/modules/utilities/cards/IdleInhibit.qml
@@ -117,6 +117,9 @@ StyledRect {
}
Behavior on implicitHeight {
- Anim {}
+ Anim {
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
}
}
diff --git a/modules/utilities/cards/Record.qml b/modules/utilities/cards/Record.qml
new file mode 100644
index 0000000..c993785
--- /dev/null
+++ b/modules/utilities/cards/Record.qml
@@ -0,0 +1,315 @@
+pragma ComponentBehavior: Bound
+
+import qs.components
+import qs.components.controls
+import qs.components.containers
+import qs.services
+import qs.config
+import qs.utils
+import Caelestia
+import Caelestia.Models
+import Quickshell
+import Quickshell.Widgets
+import QtQuick
+import QtQuick.Layouts
+
+StyledRect {
+ id: root
+
+ required property var props
+ required property var visibilities
+
+ Layout.fillWidth: true
+ implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
+
+ radius: Appearance.rounding.normal
+ color: Colours.palette.m3surfaceContainer
+
+ ColumnLayout {
+ id: layout
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.large
+ spacing: Appearance.spacing.normal
+
+ RowLayout {
+ spacing: Appearance.spacing.normal
+
+ StyledRect {
+ implicitWidth: implicitHeight
+ implicitHeight: {
+ const h = icon.implicitHeight + Appearance.padding.smaller * 2;
+ return h - (h % 2);
+ }
+
+ radius: Appearance.rounding.full
+ color: Recorder.running ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
+
+ MaterialIcon {
+ id: icon
+
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: -0.5
+ anchors.verticalCenterOffset: 1.5
+ text: "screen_record"
+ color: Recorder.running ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
+ font.pointSize: Appearance.font.size.large
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 0
+
+ StyledText {
+ Layout.fillWidth: true
+ text: qsTr("Screen Recorder")
+ font.pointSize: Appearance.font.size.normal
+ elide: Text.ElideRight
+ }
+
+ StyledText {
+ Layout.fillWidth: true
+ text: Recorder.paused ? qsTr("Recording paused") : Recorder.running ? qsTr("Recording running") : qsTr("Recording off")
+ color: Colours.palette.m3onSurfaceVariant
+ font.pointSize: Appearance.font.size.small
+ elide: Text.ElideRight
+ }
+ }
+
+ IconButton {
+ icon: "not_started"
+ checked: Recorder.running
+ type: IconButton.Filled
+ inactiveColour: Colours.palette.m3surfaceContainerHighest
+ toggle: true
+
+ function onClicked(): void {
+ Recorder.toggle();
+ }
+ }
+ }
+
+ WrapperMouseArea {
+ Layout.bottomMargin: -layout.spacing
+ Layout.fillWidth: true
+
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
+
+ RowLayout {
+ spacing: Appearance.spacing.smaller
+
+ MaterialIcon {
+ Layout.alignment: Qt.AlignVCenter
+ text: "list"
+ font.pointSize: Appearance.font.size.large
+ }
+
+ StyledText {
+ Layout.alignment: Qt.AlignVCenter
+ Layout.fillWidth: true
+ text: qsTr("Recordings")
+ font.pointSize: Appearance.font.size.normal
+ }
+
+ IconButton {
+ icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
+ type: IconButton.Text
+ label.animate: true
+
+ function onClicked(): void {
+ root.props.recordingListExpanded = !root.props.recordingListExpanded;
+ }
+ }
+ }
+ }
+
+ StyledListView {
+ id: recordingList
+
+ model: FileSystemModel {
+ path: Paths.recsdir
+ nameFilters: ["recording_*.mp4"]
+ sortReverse: true
+ }
+
+ Layout.fillWidth: true
+ Layout.rightMargin: -Appearance.spacing.small
+ implicitHeight: (Appearance.font.size.larger + Appearance.padding.small) * (root.props.recordingListExpanded ? 10 : 3)
+ clip: true
+
+ StyledScrollBar.vertical: StyledScrollBar {}
+
+ delegate: RowLayout {
+ id: recording
+
+ required property FileSystemEntry modelData
+ property string baseName
+
+ anchors.left: recordingList.contentItem.left
+ anchors.right: recordingList.contentItem.right
+ anchors.rightMargin: Appearance.spacing.small
+ spacing: Appearance.spacing.small / 2
+
+ Component.onCompleted: baseName = modelData.baseName
+
+ StyledText {
+ Layout.fillWidth: true
+ Layout.rightMargin: Appearance.spacing.small / 2
+ text: {
+ const time = recording.baseName;
+ const matches = time.match(/^recording_(\d{4})(\d{2})(\d{2})_(\d{2})-(\d{2})-(\d{2})/);
+ if (!matches)
+ return time;
+ const date = new Date(...matches.slice(1));
+ return qsTr("Recording at %1").arg(Qt.formatDateTime(date, Qt.locale()));
+ }
+ color: Colours.palette.m3onSurfaceVariant
+ elide: Text.ElideRight
+ }
+
+ IconButton {
+ icon: "play_arrow"
+ type: IconButton.Text
+
+ function onClicked(): void {
+ root.visibilities.utilities = false;
+ Quickshell.execDetached([...Config.general.apps.playback, recording.modelData.path]);
+ }
+ }
+
+ IconButton {
+ icon: "folder"
+ type: IconButton.Text
+
+ function onClicked(): void {
+ root.visibilities.utilities = false;
+ Quickshell.execDetached([...Config.general.apps.explorer, recording.modelData.path]);
+ }
+ }
+
+ IconButton {
+ icon: "delete_forever"
+ type: IconButton.Text
+ label.color: Colours.palette.m3error
+
+ function onClicked(): void {
+ CUtils.deleteFile(Qt.resolvedUrl(recording.modelData.path));
+ }
+ }
+ }
+
+ add: Transition {
+ Anim {
+ property: "opacity"
+ from: 0
+ to: 1
+ }
+ Anim {
+ property: "scale"
+ from: 0.5
+ to: 1
+ }
+ }
+
+ remove: Transition {
+ Anim {
+ property: "opacity"
+ to: 0
+ }
+ Anim {
+ property: "scale"
+ to: 0.5
+ }
+ }
+
+ displaced: Transition {
+ Anim {
+ properties: "opacity,scale"
+ to: 1
+ }
+ Anim {
+ property: "y"
+ }
+ }
+
+ Loader {
+ anchors.centerIn: parent
+
+ opacity: recordingList.count === 0 ? 1 : 0
+ active: opacity > 0
+ asynchronous: true
+
+ sourceComponent: ColumnLayout {
+ spacing: Appearance.spacing.small
+
+ MaterialIcon {
+ Layout.alignment: Qt.AlignHCenter
+ text: "scan_delete"
+ color: Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.extraLarge
+
+ opacity: root.props.recordingListExpanded ? 1 : 0
+ scale: root.props.recordingListExpanded ? 1 : 0
+ Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0
+
+ Behavior on opacity {
+ Anim {}
+ }
+
+ Behavior on scale {
+ Anim {}
+ }
+
+ Behavior on Layout.preferredHeight {
+ Anim {}
+ }
+ }
+
+ RowLayout {
+ spacing: Appearance.spacing.smaller
+
+ MaterialIcon {
+ Layout.alignment: Qt.AlignHCenter
+ text: "scan_delete"
+ color: Colours.palette.m3outline
+
+ opacity: !root.props.recordingListExpanded ? 1 : 0
+ scale: !root.props.recordingListExpanded ? 1 : 0
+ Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0
+
+ Behavior on opacity {
+ Anim {}
+ }
+
+ Behavior on scale {
+ Anim {}
+ }
+
+ Behavior on Layout.preferredWidth {
+ Anim {}
+ }
+ }
+
+ StyledText {
+ text: qsTr("No recordings found")
+ color: Colours.palette.m3outline
+ }
+ }
+ }
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+
+ Behavior on implicitHeight {
+ Anim {
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ }
+ }
+ }
+}
diff --git a/modules/utilities/cards/Toggles.qml b/modules/utilities/cards/Toggles.qml
index 8e4dfb5..6e2d227 100644
--- a/modules/utilities/cards/Toggles.qml
+++ b/modules/utilities/cards/Toggles.qml
@@ -89,7 +89,7 @@ StyledRect {
required property string icon
required property string text
property bool checked
- property bool toggle
+ property bool toggle: true
property bool internalChecked
function onClicked(): void {
diff --git a/plugin/src/Caelestia/Models/filesystemmodel.cpp b/plugin/src/Caelestia/Models/filesystemmodel.cpp
index 54807b5..eb30fa8 100644
--- a/plugin/src/Caelestia/Models/filesystemmodel.cpp
+++ b/plugin/src/Caelestia/Models/filesystemmodel.cpp
@@ -26,6 +26,10 @@ QString FileSystemEntry::name() const {
return m_fileInfo.fileName();
};
+QString FileSystemEntry::baseName() const {
+ return m_fileInfo.baseName();
+};
+
QString FileSystemEntry::parentDir() const {
return m_fileInfo.absolutePath();
};
@@ -149,6 +153,21 @@ void FileSystemModel::setShowHidden(bool showHidden) {
update();
}
+bool FileSystemModel::sortReverse() const {
+ return m_sortReverse;
+}
+
+void FileSystemModel::setSortReverse(bool sortReverse) {
+ if (m_sortReverse == sortReverse) {
+ return;
+ }
+
+ m_sortReverse = sortReverse;
+ emit sortReverseChanged();
+
+ update();
+}
+
FileSystemModel::Filter FileSystemModel::filter() const {
return m_filter;
}
@@ -164,6 +183,21 @@ void FileSystemModel::setFilter(Filter filter) {
update();
}
+QStringList FileSystemModel::nameFilters() const {
+ return m_nameFilters;
+}
+
+void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
+ if (m_nameFilters == nameFilters) {
+ return;
+ }
+
+ m_nameFilters = nameFilters;
+ emit nameFiltersChanged();
+
+ update();
+}
+
QList<FileSystemEntry*> FileSystemModel::entries() const {
return m_entries;
}
@@ -238,22 +272,22 @@ void FileSystemModel::updateEntries() {
}
void FileSystemModel::updateEntriesForDir(const QString& dir) {
- const bool recursive = m_recursive;
- const bool showHidden = m_showHidden;
+ const auto recursive = m_recursive;
+ const auto showHidden = m_showHidden;
const auto filter = m_filter;
+ const auto nameFilters = m_nameFilters;
const auto oldEntries = m_entries;
const auto baseDir = m_dir;
- const auto future = QtConcurrent::run([dir, recursive, showHidden, filter, oldEntries, baseDir](
- QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
+ const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
std::optional<QDirIterator> iter;
if (filter == Images) {
- QStringList nameFilters;
+ QStringList extraNameFilters = nameFilters;
for (const auto& format : QImageReader::supportedImageFormats()) {
- nameFilters << "*." + format;
+ extraNameFilters << "*." + format;
}
QDir::Filters filters = QDir::Files;
@@ -261,7 +295,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
filters |= QDir::Hidden;
}
- iter.emplace(dir, nameFilters, filters, flags);
+ iter.emplace(dir, extraNameFilters, filters, flags);
} else {
QDir::Filters filters;
@@ -277,7 +311,11 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) {
filters |= QDir::Hidden;
}
- iter.emplace(dir, filters, flags);
+ if (nameFilters.isEmpty()) {
+ iter.emplace(dir, filters, flags);
+ } else {
+ iter.emplace(dir, nameFilters, filters, flags);
+ }
}
QSet<QString> newPaths;
@@ -376,13 +414,16 @@ void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet
for (const auto& path : addedPaths) {
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
}
- std::sort(newEntries.begin(), newEntries.end(), &FileSystemModel::compareEntries);
+ const auto comp = [this](const FileSystemEntry* a, const FileSystemEntry* b) {
+ return compareEntries(a, b);
+ };
+ std::sort(newEntries.begin(), newEntries.end(), comp);
int insertStart = -1;
int prevRow = -1;
QList<FileSystemEntry*> batchItems;
for (const auto& entry : newEntries) {
- const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), entry, &FileSystemModel::compareEntries);
+ const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), entry, comp);
int row = static_cast<int>(it - m_entries.begin());
if (insertStart == -1) {
@@ -420,11 +461,11 @@ void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet
emit entriesChanged();
}
-bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) {
+bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const {
if (a->isDir() != b->isDir()) {
- return a->isDir();
+ return m_sortReverse ^ a->isDir();
}
- return a->relativePath().localeAwareCompare(b->relativePath()) < 0;
+ return m_sortReverse ^ (a->relativePath().localeAwareCompare(b->relativePath()) < 0);
}
} // namespace caelestia
diff --git a/plugin/src/Caelestia/Models/filesystemmodel.hpp b/plugin/src/Caelestia/Models/filesystemmodel.hpp
index 4ea5f0a..cd2240d 100644
--- a/plugin/src/Caelestia/Models/filesystemmodel.hpp
+++ b/plugin/src/Caelestia/Models/filesystemmodel.hpp
@@ -19,6 +19,7 @@ class FileSystemEntry : public QObject {
Q_PROPERTY(QString path READ path CONSTANT)
Q_PROPERTY(QString relativePath READ relativePath CONSTANT)
Q_PROPERTY(QString name READ name CONSTANT)
+ Q_PROPERTY(QString baseName READ baseName CONSTANT)
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
Q_PROPERTY(QString suffix READ suffix CONSTANT)
Q_PROPERTY(qint64 size READ size CONSTANT)
@@ -32,6 +33,7 @@ public:
[[nodiscard]] QString path() const;
[[nodiscard]] QString relativePath() const;
[[nodiscard]] QString name() const;
+ [[nodiscard]] QString baseName() const;
[[nodiscard]] QString parentDir() const;
[[nodiscard]] QString suffix() const;
[[nodiscard]] qint64 size() const;
@@ -60,7 +62,9 @@ class FileSystemModel : public QAbstractListModel {
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
+ Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
+ Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
Q_PROPERTY(QList<FileSystemEntry*> entries READ entries NOTIFY entriesChanged)
@@ -91,9 +95,15 @@ public:
[[nodiscard]] bool showHidden() const;
void setShowHidden(bool showHidden);
+ [[nodiscard]] bool sortReverse() const;
+ void setSortReverse(bool sortReverse);
+
[[nodiscard]] Filter filter() const;
void setFilter(Filter filter);
+ [[nodiscard]] QStringList nameFilters() const;
+ void setNameFilters(const QStringList& nameFilters);
+
[[nodiscard]] QList<FileSystemEntry*> entries() const;
signals:
@@ -101,7 +111,9 @@ signals:
void recursiveChanged();
void watchChangesChanged();
void showHiddenChanged();
+ void sortReverseChanged();
void filterChanged();
+ void nameFiltersChanged();
void entriesChanged();
void added(const FileSystemEntry* entry);
@@ -117,7 +129,9 @@ private:
bool m_recursive;
bool m_watchChanges;
bool m_showHidden;
+ bool m_sortReverse;
Filter m_filter;
+ QStringList m_nameFilters;
void watchDirIfRecursive(const QString& path);
void update();
@@ -125,7 +139,7 @@ private:
void updateEntries();
void updateEntriesForDir(const QString& dir);
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
- [[nodiscard]] static bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b);
+ [[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
};
} // namespace caelestia
diff --git a/plugin/src/Caelestia/cutils.cpp b/plugin/src/Caelestia/cutils.cpp
index e93a590..f0902c3 100644
--- a/plugin/src/Caelestia/cutils.cpp
+++ b/plugin/src/Caelestia/cutils.cpp
@@ -87,10 +87,6 @@ void CUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
});
}
-bool CUtils::copyFile(const QUrl& source, const QUrl& target) const {
- return this->copyFile(source, target, true);
-}
-
bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
if (!source.isLocalFile()) {
qWarning() << "CUtils::copyFile: source" << source << "is not a local file";
@@ -108,6 +104,15 @@ bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) co
return QFile::copy(source.toLocalFile(), target.toLocalFile());
}
+bool CUtils::deleteFile(const QUrl& path) const {
+ if (!path.isLocalFile()) {
+ qWarning() << "CUtils::deleteFile: path" << path << "is not a local file";
+ return false;
+ }
+
+ return QFile::remove(path.toLocalFile());
+}
+
void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) {
this->getDominantColour(item, 128, callback);
}
diff --git a/plugin/src/Caelestia/cutils.hpp b/plugin/src/Caelestia/cutils.hpp
index 892ff86..57ece14 100644
--- a/plugin/src/Caelestia/cutils.hpp
+++ b/plugin/src/Caelestia/cutils.hpp
@@ -21,8 +21,8 @@ public:
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
// clang-format on
- Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target) const;
- Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite) const;
+ Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
+ Q_INVOKABLE bool deleteFile(const QUrl& path) const;
Q_INVOKABLE void getDominantColour(QQuickItem* item, QJSValue callback);
Q_INVOKABLE void getDominantColour(QQuickItem* item, int rescaleSize, QJSValue callback);
diff --git a/services/Recorder.qml b/services/Recorder.qml
new file mode 100644
index 0000000..370bfd8
--- /dev/null
+++ b/services/Recorder.qml
@@ -0,0 +1,38 @@
+pragma Singleton
+
+import Quickshell
+import Quickshell.Io
+
+Singleton {
+ id: root
+
+ readonly property alias running: props.running
+ readonly property alias paused: props.paused
+
+ function toggle(): void {
+ Quickshell.execDetached(["caelestia", "record"]);
+ props.running = !props.running;
+ if (!props.running)
+ props.paused = false;
+ }
+
+ function togglePause(): void {
+ Quickshell.execDetached(["caelestia", "record", "-p"]);
+ props.paused = !props.paused;
+ }
+
+ PersistentProperties {
+ id: props
+
+ property bool running: false
+ property bool paused: false
+
+ reloadableId: "recorder"
+ }
+
+ Process {
+ running: true
+ command: ["pidof", "gpu-screen-recorder"]
+ onExited: code => props.running = code === 0
+ }
+}
diff --git a/utils/Paths.qml b/utils/Paths.qml
index f0dfa4d..5135910 100644
--- a/utils/Paths.qml
+++ b/utils/Paths.qml
@@ -9,6 +9,7 @@ Singleton {
readonly property string home: Quickshell.env("HOME")
readonly property string pictures: Quickshell.env("XDG_PICTURES_DIR") || `${home}/Pictures`
+ readonly property string videos: Quickshell.env("XDG_VIDEOS_DIR") || `${home}/Videos`
readonly property string data: `${Quickshell.env("XDG_DATA_HOME") || `${home}/.local/share`}/caelestia`
readonly property string state: `${Quickshell.env("XDG_STATE_HOME") || `${home}/.local/state`}/caelestia`
@@ -17,6 +18,7 @@ Singleton {
readonly property string imagecache: `${cache}/imagecache`
readonly property string wallsdir: Quickshell.env("CAELESTIA_WALLPAPERS_DIR") || absolutePath(Config.paths.wallpaperDir)
+ readonly property string recsdir: Quickshell.env("CAELESTIA_RECORDINGS_DIR") || `${videos}/Recordings`
readonly property string libdir: Quickshell.env("CAELESTIA_LIB_DIR") || "/usr/lib/caelestia"
function toLocalFile(path: url): string {