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(KF6Screen CONFIG REQUIRED)
find_package(KWayland CONFIG REQUIRED)
find_package(KPipeWire ${PROJECT_DEP_VERSION} REQUIRED)
find_package(PkgConfig REQUIRED)

View file

@ -15,6 +15,8 @@ target_link_libraries(recordplugin PRIVATE
KF6::ConfigGui
KF6::I18n
KF6::Notifications
K::KPipeWire
K::KPipeWireRecord
)
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
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;
}
}
}
}

View file

@ -1,8 +1,6 @@
/*
* SPDX-FileCopyrightText: 2022 by Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
// SPDX-FileCopyrightText: 2022-2025 by Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "recordutil.h"
@ -11,6 +9,7 @@
#include <QStandardPaths>
#include <KFileUtils>
#include <KLocalizedString>
#include <KNotification>
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);
}

View file

@ -1,8 +1,6 @@
/*
* SPDX-FileCopyrightText: 2022 by Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
// SPDX-FileCopyrightText: 2022-2025 by Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -10,15 +8,28 @@
#include <QVariantMap>
#include <qqmlregistration.h>
#include <PipeWireRecord>
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};
};