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++.
This commit is contained in:
Devin Lin 2025-06-13 21:16:20 -04:00
parent 92a1cfc740
commit e4919690b4
5 changed files with 205 additions and 57 deletions

View file

@ -81,6 +81,7 @@ find_package(PlasmaQuick CONFIG REQUIRED)
find_package(PlasmaActivities CONFIG REQUIRED) find_package(PlasmaActivities CONFIG REQUIRED)
find_package(KF6Screen CONFIG REQUIRED) find_package(KF6Screen CONFIG REQUIRED)
find_package(KWayland CONFIG REQUIRED) find_package(KWayland CONFIG REQUIRED)
find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)

View file

@ -15,6 +15,8 @@ target_link_libraries(recordplugin PRIVATE
KF6::ConfigGui KF6::ConfigGui
KF6::I18n KF6::I18n
KF6::Notifications KF6::Notifications
K::KPipeWire
K::KPipeWireRecord
) )
ecm_finalize_qml_module(recordplugin) ecm_finalize_qml_module(recordplugin)

View file

@ -1,65 +1,49 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org> // SPDX-FileCopyrightText: 2022-2025 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15 import QtQuick
import QtQuick.Window 2.15 import QtQuick.Window
import org.kde.plasma.private.mobileshell.state as MobileShellState import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.pipewire.record 0.1 as PWRec import org.kde.taskmanager as TaskManager
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.plasma.quicksetting.record import org.kde.plasma.quicksetting.record
import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS import org.kde.plasma.private.mobileshell.quicksettingsplugin as QS
QS.QuickSetting { QS.QuickSetting {
id: root id: root
text: switch (record.state) {
case PWRec.PipeWireRecord.Idle: text: RecordUtil.quickSettingText
return i18n("Record Screen") status: RecordUtil.quickSettingStatus
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…")
}
icon: "camera-video-symbolic" icon: "camera-video-symbolic"
enabled: false enabled: RecordUtil.isRecording
available: record.encoder != PWRec.PipeWireRecord.NoEncoder available: true
function toggle() { function toggle() {
if (!record.active) { if (RecordUtil.isRecording) {
// See this https://invent.kde.org/plasma/kpipewire/-/blob/eb21912e7e0ce5a70c6f906c6e5a20f56cc6783e/src/pipewirerecord.cpp#L82 RecordUtil.stopRecording();
switch (record.encoder) { waylandItem.outputName = '';
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;
}
} else { } 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 { TaskManager.ScreencastingRequest {
id: waylandItem 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;
}
}
} }
} }

View file

@ -1,8 +1,6 @@
/* // SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2022 by Devin Lin <devin@kde.org> // SPDX-FileCopyrightText: 2022-2025 by Devin Lin <devin@kde.org>
* // SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "recordutil.h" #include "recordutil.h"
@ -11,6 +9,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <KFileUtils> #include <KFileUtils>
#include <KLocalizedString>
#include <KNotification> #include <KNotification>
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
@ -18,6 +17,80 @@ using namespace Qt::StringLiterals;
RecordUtil::RecordUtil(QObject *parent) RecordUtil::RecordUtil(QObject *parent)
: 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) QString RecordUtil::videoLocation(const QString &name)
@ -42,3 +115,63 @@ void RecordUtil::showNotification(const QString &title, const QString &text, con
notif->setText(text); notif->setText(text);
notif->sendEvent(); 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);
}

View file

@ -1,8 +1,6 @@
/* // SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2022 by Devin Lin <devin@kde.org> // SPDX-FileCopyrightText: 2022-2025 by Devin Lin <devin@kde.org>
* // SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once #pragma once
@ -10,15 +8,28 @@
#include <QVariantMap> #include <QVariantMap>
#include <qqmlregistration.h> #include <qqmlregistration.h>
#include <PipeWireRecord>
class RecordUtil : public QObject class RecordUtil : public QObject
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_SINGLETON 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: public:
RecordUtil(QObject *parent = nullptr); 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) * Allows us to get a filename in the standard videos directory (~/Videos by default)
* with a name that starts with @p name * with a name that starts with @p name
@ -28,7 +39,24 @@ public:
* @see QStandardPaths::writableLocation() * @see QStandardPaths::writableLocation()
* @see KFileUtil::suggestName() * @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};
}; };