From 4b221e2fd5b7faf4747cff6c590b6ddc38870901 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sun, 14 Sep 2025 16:59:19 +1000 Subject: utilities: add recording control --- components/controls/IconButton.qml | 68 +++++ components/controls/StyledScrollBar.qml | 14 +- config/GeneralConfig.qml | 2 + modules/utilities/Content.qml | 6 + modules/utilities/Wrapper.qml | 7 + modules/utilities/cards/IdleInhibit.qml | 5 +- modules/utilities/cards/Record.qml | 315 ++++++++++++++++++++++++ modules/utilities/cards/Toggles.qml | 2 +- plugin/src/Caelestia/Models/filesystemmodel.cpp | 67 ++++- plugin/src/Caelestia/Models/filesystemmodel.hpp | 16 +- plugin/src/Caelestia/cutils.cpp | 13 +- plugin/src/Caelestia/cutils.hpp | 4 +- services/Recorder.qml | 38 +++ utils/Paths.qml | 2 + 14 files changed, 536 insertions(+), 23 deletions(-) create mode 100644 components/controls/IconButton.qml create mode 100644 modules/utilities/cards/Record.qml create mode 100644 services/Recorder.qml 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 terminal: ["foot"] property list audio: ["pavucontrol"] + property list playback: ["mpv"] + property list 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 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, QSet>>& promise) { + const auto future = QtConcurrent::run([=](QPromise, QSet>>& promise) { const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags; std::optional 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 newPaths; @@ -376,13 +414,16 @@ void FileSystemModel::applyChanges(const QSet& 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 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(it - m_entries.begin()); if (insertStart == -1) { @@ -420,11 +461,11 @@ void FileSystemModel::applyChanges(const QSet& 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 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 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& removedPaths, const QSet& 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 { -- cgit v1.2.3-freya