From e4919690b412d2a88d66e54060d3af98923404e4 Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Fri, 13 Jun 2025 21:16:20 -0400 Subject: [PATCH] quicksettings/record: Make it more efficient on initial load Try to reduce the amount of preloaded objects that aren't needed until the quick setting is toggled. Also port the the kpipewire interaction parts to C++. --- CMakeLists.txt | 1 + quicksettings/record/CMakeLists.txt | 2 + .../record/package/contents/ui/main.qml | 74 ++++----- quicksettings/record/recordutil.cpp | 143 +++++++++++++++++- quicksettings/record/recordutil.h | 42 ++++- 5 files changed, 205 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8588ec38..8a2ab6e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ find_package(PlasmaQuick CONFIG REQUIRED) find_package(PlasmaActivities CONFIG REQUIRED) find_package(KF6Screen CONFIG REQUIRED) find_package(KWayland CONFIG REQUIRED) +find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED) find_package(PkgConfig REQUIRED) diff --git a/quicksettings/record/CMakeLists.txt b/quicksettings/record/CMakeLists.txt index 72b3e52b..1265a065 100644 --- a/quicksettings/record/CMakeLists.txt +++ b/quicksettings/record/CMakeLists.txt @@ -15,6 +15,8 @@ target_link_libraries(recordplugin PRIVATE KF6::ConfigGui KF6::I18n KF6::Notifications + K::KPipeWire + K::KPipeWireRecord ) ecm_finalize_qml_module(recordplugin) diff --git a/quicksettings/record/package/contents/ui/main.qml b/quicksettings/record/package/contents/ui/main.qml index 439113aa..041568ef 100644 --- a/quicksettings/record/package/contents/ui/main.qml +++ b/quicksettings/record/package/contents/ui/main.qml @@ -1,65 +1,49 @@ -// SPDX-FileCopyrightText: 2022 Devin Lin +// SPDX-FileCopyrightText: 2022-2025 Devin Lin // SPDX-License-Identifier: LGPL-2.0-or-later -import QtQuick 2.15 -import QtQuick.Window 2.15 +import QtQuick +import QtQuick.Window import org.kde.plasma.private.mobileshell.state as MobileShellState -import org.kde.pipewire.record 0.1 as PWRec -import org.kde.taskmanager 0.1 as TaskManager +import org.kde.taskmanager as TaskManager import org.kde.plasma.quicksetting.record import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS QS.QuickSetting { id: root - text: switch (record.state) { - case PWRec.PipeWireRecord.Idle: - return i18n("Record Screen") - case PWRec.PipeWireRecord.Recording: - return i18n("Recording…") - case PWRec.PipeWireRecord.Rendering: - i18n("Writing…") - } - status: switch(record.state) { - case PWRec.PipeWireRecord.Idle: - return i18n("Tap to start recording") - case PWRec.PipeWireRecord.Recording: - return i18n("Screen is being captured…") - case PWRec.PipeWireRecord.Rendering: - i18n("Please wait…") - } + + text: RecordUtil.quickSettingText + status: RecordUtil.quickSettingStatus icon: "camera-video-symbolic" - enabled: false - available: record.encoder != PWRec.PipeWireRecord.NoEncoder + enabled: RecordUtil.isRecording + available: true function toggle() { - if (!record.active) { - // See this https://invent.kde.org/plasma/kpipewire/-/blob/eb21912e7e0ce5a70c6f906c6e5a20f56cc6783e/src/pipewirerecord.cpp#L82 - switch (record.encoder) { - case PWRec.PipeWireRecord.H264Main: - case PWRec.PipeWireRecord.H264Baseline: - record.output = RecordUtil.videoLocation("screen-recording.mp4"); - break; - case PWRec.PipeWireRecord.VP8: - case PWRec.PipeWireRecord.VP9: - record.output = RecordUtil.videoLocation("screen-recording.webm"); - break; - } + if (RecordUtil.isRecording) { + RecordUtil.stopRecording(); + waylandItem.outputName = ''; } else { - RecordUtil.showNotification(i18n("New Screen Recording"), i18n("New Screen Recording saved in %1", record.output), record.output); + // Start recording only when waylandItem's nodeId updates + waylandItem.startRecordingRequest = true; + waylandItem.outputName = Screen.name; } - - enabled = !enabled - MobileShellState.ShellDBusClient.closeActionDrawer(); } - PWRec.PipeWireRecord { - id: record - nodeId: waylandItem.nodeId - active: root.enabled - } TaskManager.ScreencastingRequest { id: waylandItem - outputName: root.enabled ? Screen.name : "" + property bool startRecordingRequest: false + + onNodeIdChanged: { + if (startRecordingRequest) { + let status = RecordUtil.startRecording(waylandItem.nodeId); + if (status) { + MobileShellState.ShellDBusClient.closeActionDrawer(); + } else { + waylandItem.outputName = ''; + } + + startRecordingRequest = false; + } + } } } diff --git a/quicksettings/record/recordutil.cpp b/quicksettings/record/recordutil.cpp index e8a1cb08..d9a956a6 100644 --- a/quicksettings/record/recordutil.cpp +++ b/quicksettings/record/recordutil.cpp @@ -1,8 +1,6 @@ -/* - * SPDX-FileCopyrightText: 2022 by Devin Lin - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ +// SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez +// SPDX-FileCopyrightText: 2022-2025 by Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later #include "recordutil.h" @@ -11,6 +9,7 @@ #include #include +#include #include using namespace Qt::StringLiterals; @@ -18,6 +17,80 @@ using namespace Qt::StringLiterals; RecordUtil::RecordUtil(QObject *parent) : QObject{parent} { + updateQuickSettingText(); + updateQuickSettingStatus(); +} + +bool RecordUtil::startRecording(int nodeId) +{ + if (!m_pipeWireRecord) { + createPipeWireRecord(); + } + + if (m_pipeWireRecord->isActive()) { + return false; + } + + switch (m_pipeWireRecord->encoder()) { + case PipeWireRecord::H264Main: + case PipeWireRecord::H264Baseline: + m_pipeWireRecord->setOutput(videoLocation("screen-recording.mp4")); + break; + case PipeWireRecord::VP8: + case PipeWireRecord::VP9: + m_pipeWireRecord->setOutput(videoLocation("screen-recording.webm")); + break; + case PipeWireRecord::WebP: + m_pipeWireRecord->setOutput(videoLocation("screen-recording.webp")); + break; + case PipeWireRecord::Gif: + m_pipeWireRecord->setOutput(videoLocation("screen-recording.gif")); + break; + case PipeWireRecord::NoEncoder: + default: + m_quickSettingStatus = i18n("No encoders available for recording"); + Q_EMIT quickSettingStatusChanged(); + qWarning() << "No video encoders available for screen recording!"; + return false; + } + + m_pipeWireRecord->setNodeId(nodeId); + m_pipeWireRecord->start(); + + qDebug() << "Started recording screen with nodeId" << nodeId << "to file" << m_pipeWireRecord->output() << videoLocation("screen-recording.webm"); + return true; +} + +void RecordUtil::stopRecording() +{ + if (!m_pipeWireRecord) { + return; + } + if (!m_pipeWireRecord->isActive()) { + return; + } + + m_pipeWireRecord->stop(); + showNotification(i18n("New Screen Recording"), i18n("New Screen Recording saved in %1", m_pipeWireRecord->output()), m_pipeWireRecord->output()); +} + +QString RecordUtil::quickSettingText() const +{ + return m_quickSettingText; +} + +QString RecordUtil::quickSettingStatus() const +{ + return m_quickSettingStatus; +} + +bool RecordUtil::isRecording() const +{ + if (!m_pipeWireRecord) { + return false; + } + + return m_pipeWireRecord->isActive(); } QString RecordUtil::videoLocation(const QString &name) @@ -42,3 +115,63 @@ void RecordUtil::showNotification(const QString &title, const QString &text, con notif->setText(text); notif->sendEvent(); } + +void RecordUtil::updateQuickSettingText() +{ + QString defaultText = i18nc("@action:button", "Record Screen"); + if (!m_pipeWireRecord) { + m_quickSettingText = defaultText; + Q_EMIT quickSettingTextChanged(); + return; + } + + switch (m_pipeWireRecord->state()) { + case PipeWireRecord::Recording: + m_quickSettingText = i18nc("@info:status", "Recording…"); + break; + case PipeWireRecord::Rendering: + m_quickSettingText = i18nc("@info:status", "Writing…"); + break; + case PipeWireRecord::Idle: + default: + m_quickSettingText = defaultText; + break; + } + + Q_EMIT quickSettingTextChanged(); +} + +void RecordUtil::updateQuickSettingStatus() +{ + QString defaultText = i18n("Tap to start recording"); + + if (!m_pipeWireRecord) { + m_quickSettingStatus = defaultText; + Q_EMIT quickSettingStatusChanged(); + return; + } + + switch (m_pipeWireRecord->state()) { + case PipeWireRecord::Recording: + m_quickSettingStatus = i18n("Screen is being captured…"); + break; + case PipeWireRecord::Rendering: + m_quickSettingStatus = i18n("Please wait…"); + break; + case PipeWireRecord::Idle: + default: + m_quickSettingStatus = defaultText; + break; + } + + Q_EMIT quickSettingStatusChanged(); +} + +void RecordUtil::createPipeWireRecord() +{ + m_pipeWireRecord = new PipeWireRecord{this}; + + connect(m_pipeWireRecord, &PipeWireRecord::stateChanged, this, &RecordUtil::updateQuickSettingText); + connect(m_pipeWireRecord, &PipeWireRecord::stateChanged, this, &RecordUtil::updateQuickSettingStatus); + connect(m_pipeWireRecord, &PipeWireRecord::activeChanged, this, &RecordUtil::isRecordingChanged); +} \ No newline at end of file diff --git a/quicksettings/record/recordutil.h b/quicksettings/record/recordutil.h index b57142bc..33e69738 100644 --- a/quicksettings/record/recordutil.h +++ b/quicksettings/record/recordutil.h @@ -1,8 +1,6 @@ -/* - * SPDX-FileCopyrightText: 2022 by Devin Lin - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ +// SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez +// SPDX-FileCopyrightText: 2022-2025 by Devin Lin +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -10,15 +8,28 @@ #include #include +#include + class RecordUtil : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON + Q_PROPERTY(QString quickSettingText READ quickSettingText NOTIFY quickSettingTextChanged) + Q_PROPERTY(QString quickSettingStatus READ quickSettingStatus NOTIFY quickSettingStatusChanged) + Q_PROPERTY(bool isRecording READ isRecording NOTIFY isRecordingChanged) + public: RecordUtil(QObject *parent = nullptr); + Q_INVOKABLE bool startRecording(int nodeId); + Q_INVOKABLE void stopRecording(); + + QString quickSettingText() const; + QString quickSettingStatus() const; + bool isRecording() const; + /** * Allows us to get a filename in the standard videos directory (~/Videos by default) * with a name that starts with @p name @@ -28,7 +39,24 @@ public: * @see QStandardPaths::writableLocation() * @see KFileUtil::suggestName() */ - Q_INVOKABLE QString videoLocation(const QString &name); + QString videoLocation(const QString &name); - Q_INVOKABLE void showNotification(const QString &title, const QString &text, const QString &filePath); + void showNotification(const QString &title, const QString &text, const QString &filePath); + +Q_SIGNALS: + void quickSettingTextChanged(); + void quickSettingStatusChanged(); + void isRecordingChanged(); + +private: + void updateQuickSettingText(); + void updateQuickSettingStatus(); + + void createPipeWireRecord(); + + QString m_quickSettingText; + QString m_quickSettingStatus; + + // Only created when needed + PipeWireRecord *m_pipeWireRecord{nullptr}; };