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}; };