Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

@@ -0,0 +1,35 @@
#
# $Id$
# $HeadURL$
# $Author$
# Created by Filip Biedniak, SLSE, 2020-06-15
#
SET(scr_gui_SRCS
scr_gui_plugin.cpp
scr_gui_plugin.h
screen_recording_dialog.cpp
screen_recording_dialog.h
screen_recording_worker.cpp
screen_recording_worker.h
)
NGL_WRAP_UI(scr_gui_SRCS
screen_recording_dialog.ui
)
NGL_ADD_LIBRARY(WS ScrScreenRecordingGuiPlugin MODULE ${scr_gui_SRCS})
NGL_TARGET_LINK_LIBRARIES(ScrScreenRecordingGuiPlugin
NGLLogger
LtsSession
GuiPluginInterface
situationMap
SituationMapGuiPluginInterface
Qt5::Widgets
LtsWidgets
svcScrDummy
)
NGL_INSTALL_TARGET(ScrScreenRecordingGuiPlugin)
@@ -0,0 +1,186 @@
/**
* @RDE_HEADER@
* Gui Plugin for ScreenRecorder - Party
* $Id$
* $HeadURL$
* $Author$
* Created by Filip Biedziak, NLE, 2020-06-15
*/
#include "scr_gui_plugin.h"
#include "screen_recording_dialog.h"
#include "screen_recording_worker.h"
#include <LtsSession/lts_session.h>
#include <LtsWidgets/lts_base_layout_widget.h>
#include <LtsWidgets/lts_tool_bar_area.h>
#include <LtsWidgets/lts_drag_icon.h>
#include <Services/ScreenRecording/scr_operator_rights.h>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QToolButton>
#include <QTimer>
namespace ScreenRecording
{
#define SCREEN_RECORDING_IDENTIFIER QLatin1String("scrScreenRecorder")
#define TAKE_SCREENSHOT_IDENTIFIER QLatin1String("scrTakeScreenshot")
std::shared_ptr<GuiPluginBase::Interface> GuiPlugin::createGuiInterface(LtsSession::LtsSession *session)
{
return std::make_shared<InterfaceImpl>(session);
}
InterfaceImpl::InterfaceImpl(LtsSession::LtsSession *session)
: GuiPluginBase::Interface(session, NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("ScreenRecording")) // TODO: log name
, m_worker(new ScreenRecordingWorker(this))
{
}
bool InterfaceImpl::fetchInterfaces()
{
return true;
}
void InterfaceImpl::clearInterfaces()
{
}
QString InterfaceImpl::menuGroup() const
{
return tr("Screen Recording");
}
QVector<LtsWidgets::PluginData> InterfaceImpl::getActions()
{
QVector<LtsWidgets::PluginData> ret;
LtsWidgets::PluginData plDataScreenRec;
plDataScreenRec.menuGroup = menuGroup();
plDataScreenRec.menuText = tr("Record screen");
plDataScreenRec.identifier = SCREEN_RECORDING_IDENTIFIER;
plDataScreenRec.menuIcon = QIcon(QLatin1String("icon:rec_screen.svg"));
plDataScreenRec.operatorRight = OperatorRights::ScreenRecording::RecordScreen;
plDataScreenRec.exercisePart = LtsWidgets::ExercisePart::eSpecial;
plDataScreenRec.directFunc = [this](const LtsWidgets::PluginData &) { onStartScreenRecorder(); };
LtsWidgets::PluginData plDataTakeScreen;
plDataTakeScreen.menuGroup = menuGroup();
plDataTakeScreen.menuText = tr("Take screenshot");
plDataTakeScreen.identifier = TAKE_SCREENSHOT_IDENTIFIER;
plDataTakeScreen.menuIcon = QIcon(QLatin1String("icon:screenshot.svg"));
plDataTakeScreen.operatorRight = OperatorRights::ScreenRecording::TakeSnapshot;
plDataTakeScreen.exercisePart = LtsWidgets::ExercisePart::eSpecial;
plDataTakeScreen.directFunc = [this](const LtsWidgets::PluginData &) { onTakeScreenshot(); };
ret.append(plDataScreenRec);
ret.append(plDataTakeScreen);
return ret;
}
// ------------------------------------------------------------------------------------------------
// PRIVATE MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
QWidget *InterfaceImpl::onStartScreenRecorder()
{
if (m_worker->isBusy())
{
if (m_worker->stopRecording())
onUpdateGui();
return nullptr;
} // if
m_dialog = new ScreenRecordingDialog(menuGroup());
if (!m_dialog->init(m_session))
return nullptr;
connect(m_dialog, &ScreenRecordingDialog::startRecording,
m_worker, &ScreenRecordingWorker::onStartRecording);
connect(m_dialog, &ScreenRecordingDialog::startRecording,
this, &InterfaceImpl::onUpdateGui);
m_dialog->show();
return m_dialog;
}
QWidget *InterfaceImpl::onTakeScreenshot()
{
QString recFile = QDir(ScreenRecordingWorker::s_screenshotTmpDir).filePath(QLatin1String("screenshot_") +
QDateTime::currentDateTime().toString(Qt::ISODate).replace(QLatin1Char(':'), QLatin1Char('-')) +
QLatin1String(".png"));
recFile = QFileDialog::getSaveFileName(nullptr,
tr("Save Screenshot"),
recFile,
tr("Images (*.jpg *.jpeg *.png);;All files (*.*)"));
if (recFile.isEmpty())
return nullptr;
QFileInfo fileInfo{recFile};
m_worker->takeScreenshot(fileInfo, m_session->baseLayout());
return nullptr;
}
// ------------------------------------------------------------------------------------------------
// PRIVATE SLOTS
// ------------------------------------------------------------------------------------------------
void InterfaceImpl::onUpdateGui()
{
// add tool button to show red blinking light during recording
if (!m_toolBtn)
{
m_toolBtn = new QToolButton();
m_timer = new QTimer(m_toolBtn);
m_toolBtn->setIcon(m_recIcon);
m_toolBtn->setIconSize(QSize{24, 24});
m_toolBtn->setStyleSheet(QLatin1String("QToolButton{border: none} QToolButton:hover{background: none; border: none}"));
connect(m_timer, &QTimer::timeout, this, [this]()
{
m_toolBtn->setIcon((m_counter++ % 2) ? m_recIcon : QIcon());
m_toolBtn->setToolTip(QDateTime::fromSecsSinceEpoch(m_counter, Qt::UTC).toString(QLatin1String("hh:mm:ss")));
});
}
QPointer<LtsWidgets::LtsDragIcon> menuBtn = m_session->baseLayout()->findChild<LtsWidgets::LtsDragIcon *>(SCREEN_RECORDING_IDENTIFIER);
QIcon icon = QIcon(QLatin1String("icon:rec_screen.svg"));
if (m_worker->isBusy())
{
icon = QIcon(QLatin1String("icon:avm_stop.svg"));
menuBtn->setText(QLatin1String("Stop recording"));
m_session->baseLayout()->addNotification(m_toolBtn);
m_toolBtn->show();
m_counter = 0;
m_timer->start(1000);
} // if
else
{
menuBtn->setText(QLatin1String("Record screen"));
m_timer->stop();
m_toolBtn->hide();
} // else
menuBtn->setIcon(icon);
// update toolbar icon if it exists
for (auto &it : m_session->baseLayout()->getToolbar()->findChildren<LtsWidgets::LtsDragIcon *>()) // findChild + identifier does not work for some reason
{
if (it->identifier() == SCREEN_RECORDING_IDENTIFIER)
{
it->setIcon(icon);
break;
} // if
} // for
}
#undef TAKE_SCREENSHOT_IDENTIFIER
#undef SCREEN_RECORDING_IDENTIFIER
} // namespace ScreenRecording
@@ -0,0 +1,124 @@
/**
* @RDE_HEADER@
* Gui Plugin for ScreenRecorder - Party
* $Id$
* $HeadURL$
* $Author$
* Created by Filip Biedziak, NLE, 2020-06-15
*/
#pragma once
#include <PluginInterfaces/gui_plugin_interface.h>
class QToolButton;
class QIcon;
namespace ScreenRecording
{
#define RECORDING_TEMP_DIR QDir::tempPath() + QLatin1String("/screenrecording/")
#define SCREENSHOT_TEMP_DIR QDir::tempPath() + QLatin1String("/screenshot/")
class ScreenRecordingDialog;
class ScreenRecordingWorker;
class GuiPlugin final : public QObject, public GuiPluginBase::Plugin
{
/**
* \brief GuiPlugin provides an interface for recording the screen.
*
* FFmpeg is used to record the screen with the user being able to adjust the settings, such as:
* - What will be recorded
* - Where it will be saved
* - How the video will be encoded
* To start FFmpeg, a QProcess is used to start it as a command line application via a worker
* class ScreenRecordingWorker.
*/
Q_PLUGIN_METADATA(IID "gui:3:scr.screenrecorder")
Q_INTERFACES(GuiPluginBase::Plugin)
Q_OBJECT
public:
GuiPlugin() = default;
~GuiPlugin() = default;
QString pluginName() override { return QStringLiteral("ScreenRecording.GuiPlugin.ScreenRecorder"); }
std::shared_ptr<GuiPluginBase::Interface> createGuiInterface(LtsSession::LtsSession *session) override;
bool availableInPartition(IDL::PartitionType partition) const override
{
return (partition == IDL::PartitionType::Global ||
partition == IDL::PartitionType::Exercise ||
partition == IDL::PartitionType::Replay);
}
};
class InterfaceImpl final : public QObject, public GuiPluginBase::Interface
{
Q_OBJECT
public:
explicit InterfaceImpl(LtsSession::LtsSession *session);
bool fetchInterfaces() override;
void clearInterfaces() override;
QString menuGroup() const override;
QVector<LtsWidgets::PluginData> getActions() override;
bool availableInPartition(IDL::PartitionType partition) const override
{
return partition == IDL::PartitionType::Global ||
partition == IDL::PartitionType::Exercise ||
partition == IDL::PartitionType::Replay;
}
// ------------------------------------------------------------------------------------------------
// PRIVATE MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
private:
/**
* @brief onStartScreenRecorder opens the screen recording dialog.
*
* If there is recording already in process, this will not open a dialog, but stop the recording
* using the worker class' member function ScreenRecordingWorker::stopProcess() instead. It will
* also paint the button in the menu to its default style.
* @return screen recording dialog
*/
QWidget *onStartScreenRecorder();
/**
* @brief onTakeScreenshot takes a snapshot of the application.
*
* This will prompt a save file dialog to determine where to save the screenshot. The worker
* class is then called to make the snapshot using ScreenRecordingWorker::takeScreenshot(). No
* screenshot is taken if the file path is empty.
* @return nullptr
*/
QWidget *onTakeScreenshot();
// ------------------------------------------------------------------------------------------------
// PRIVATE SLOTS
// ------------------------------------------------------------------------------------------------
private slots:
/**
* @brief onUpdateGui paints the menu button to reflect the recording state.
*
* This will update the Gui to emphasize the recording state. The icon in the menu will be
* replaced by a different icon and a recording notification is shown or hidden. Uses the worker
* class' member function ScreenRecordingWorker::isBusy() to determine whether recording is
* taking place.
*/
void onUpdateGui();
private:
ScreenRecordingWorker *m_worker{nullptr};
ScreenRecordingDialog *m_dialog{nullptr};
QToolButton *m_toolBtn{nullptr};
QTimer *m_timer{nullptr};
QIcon m_recIcon{ QLatin1String("icon:rec_on.svg") };
quint32 m_counter{ 0 };
};
} // namespace ScreenRecording
@@ -0,0 +1,153 @@
/**
* @RDE_HEADER@
* ScreenRecording - Dialog
* $Id$
* $HeadURL$
* $Author$
* Created by Filip Biedziak, NLE, 2020-06-23
*/
#include "screen_recording_dialog.h"
#include "ui_screen_recording_dialog.h"
#include <LtsSession/lts_session.h>
#include <LtsWidgets/lts_base_layout_widget.h>
#include <LtsWidgets/lts_tab_widget.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QtWidgets/QFileDialog>
namespace ScreenRecording
{
ScreenRecordingDialog::ScreenRecordingDialog(const QString &menuGroup, QWidget *parent, const LtsWidgets::Behaviour &behaviour)
: LtsFramelessDialog(parent, behaviour)
, m_ui(new Ui::ScreenRecordingDialog)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("ScreenRecording"))
{
Q_UNUSED(menuGroup);
m_ui->setupUi(this);
m_recAreaWidget = new QWidget();
connect(m_ui->pbBrowseRecordingFile, &QPushButton::clicked, this, &ScreenRecordingDialog::onBrowseRecordingFile);
connect(m_ui->cobRecordingArea, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ScreenRecordingDialog::onChangeRecordingArea);
connect(m_ui->chbShowArea, &QCheckBox::clicked, m_recAreaWidget, &QWidget::setVisible);
connect(m_ui->chbRecordTitleBar, &QCheckBox::clicked, this, &ScreenRecordingDialog::onChangeRecordingArea);
connect(m_ui->ltsDialogButtonBox->getButton(LtsWidgets::DialogButton::Ok), &QPushButton::clicked, this, &ScreenRecordingDialog::onRecord);
connect(m_ui->ltsDialogButtonBox->getButton(LtsWidgets::DialogButton::Close), &QPushButton::clicked, this, &ScreenRecordingDialog::onCancel);
}
ScreenRecordingDialog::~ScreenRecordingDialog()
{
delete m_ui;
}
// ------------------------------------------------------------------------------------------------
// PUBLIC MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
bool ScreenRecordingDialog::init(LtsSession::LtsSession *session)
{
m_baseLayoutPtr = session->baseLayout();
if (m_baseLayoutPtr == nullptr)
{
ERROR_LOG(m_logger) << "base layout is nullptr";
return false;
} // if
m_recAreaWidget->setWindowOpacity(0.5);
m_recAreaWidget->setStyleSheet(QLatin1String("border: 5px solid red"));
m_recAreaWidget->setWindowFlags(Qt::SubWindow | Qt::FramelessWindowHint);
m_ui->leRecordingFile->setText(QDir(ScreenRecordingWorker::s_recordingTmpDir).filePath(QLatin1String("screenrecording_") +
QDateTime::currentDateTime().toString(Qt::ISODate).replace(QLatin1Char(':'), QLatin1Char('-')) +
QLatin1String(".") + ScreenRecordingWorker::s_videoFormat));
for (const auto &frameRate : allFrameRates)
m_ui->cobFrameRate->addItem(ScreenRecordingWorker::frameRateFromToString(frameRate), QVariant::fromValue(frameRate));
m_ui->cobFrameRate->setCurrentIndex(m_ui->cobFrameRate->findData(QVariant::fromValue(ScreenRecordingWorker::FrameRate::Medium)));
for (const auto &videoCodec : allVideoCodecs)
m_ui->cobVideoCodec->addItem(ScreenRecordingWorker::videoCodecToString(videoCodec), QVariant::fromValue(videoCodec));
m_ui->cobVideoCodec->setCurrentIndex(m_ui->cobVideoCodec->findData(QVariant::fromValue(ScreenRecordingWorker::VideoCodec::H264)));
for (const auto &recArea : allRecordingAreas)
m_ui->cobRecordingArea->addItem(ScreenRecordingWorker::recordingAreaToString(recArea), QVariant::fromValue(recArea));
m_ui->cobRecordingArea->setCurrentIndex(m_ui->cobRecordingArea->findData(QVariant::fromValue(ScreenRecordingWorker::RecordingArea::MainArea)));
return true;
}
// ------------------------------------------------------------------------------------------------
// PRIVATE SLOTS
// ------------------------------------------------------------------------------------------------
void ScreenRecordingDialog::onBrowseRecordingFile()
{
QString recFile = QFileDialog::getSaveFileName(this,
tr("Save Screen Recording"),
m_ui->leRecordingFile->text(),
tr("Videos (*.mp4);;All files (*.*)"));
if (!recFile.isEmpty())
m_ui->leRecordingFile->setText(recFile);
}
void ScreenRecordingDialog::onChangeRecordingArea()
{
QWidget *mainWidgetPtr = m_baseLayoutPtr->getTabFromLayoutArea(LtsWidgets::LayoutArea::eMainTab);
QPoint point = m_ui->chbRecordTitleBar->isChecked() ? m_baseLayoutPtr->mapToGlobal({})
: m_baseLayoutPtr->getTabFromLayoutArea(LtsWidgets::LayoutArea::eInfoTab)->mapToGlobal({});
QSize size = {m_baseLayoutPtr->width(),
m_ui->chbRecordTitleBar->isChecked() ? m_baseLayoutPtr->height() : m_baseLayoutPtr->getMenu()->height()};
switch (m_ui->cobRecordingArea->currentData().value<ScreenRecordingWorker::RecordingArea>())
{
case ScreenRecordingWorker::RecordingArea::MainArea:
point = {mainWidgetPtr->mapToGlobal({}).x(),
m_ui->chbRecordTitleBar->isChecked() ? m_baseLayoutPtr->mapToGlobal({}).y()
: mainWidgetPtr->mapToGlobal({}).y()};
size = {mainWidgetPtr->width(),
m_ui->chbRecordTitleBar->isChecked() ? mainWidgetPtr->mapToGlobal(mainWidgetPtr->geometry().bottomLeft()).y() - m_baseLayoutPtr->mapToGlobal({}).y() + 1
: mainWidgetPtr->height()};
break;
case ScreenRecordingWorker::RecordingArea::LeftOfMenu:
size.setWidth(mainWidgetPtr->mapToGlobal(mainWidgetPtr->geometry().bottomRight()).x() - m_baseLayoutPtr->mapToGlobal({}).x() + 1);
break;
default:
break;
}
m_recAreaWidget->setGeometry({point, size});
}
void ScreenRecordingDialog::onRecord()
{
ScreenRecordingWorker::RecordingData recData;
recData.filePath.setFile(m_ui->leRecordingFile->text().endsWith(QLatin1String(".mp4")) ? m_ui->leRecordingFile->text()
: (m_ui->leRecordingFile->text() + QLatin1String(".mp4")));
recData.frameRate = m_ui->cobFrameRate->currentData().value<ScreenRecordingWorker::FrameRate>();
recData.recArea = m_recAreaWidget->geometry();
recData.videoCodec = m_ui->cobVideoCodec->currentData().value<ScreenRecordingWorker::VideoCodec>();
m_recAreaWidget->close();
m_recAreaWidget->deleteLater();
m_recAreaWidget = nullptr;
emit startRecording(recData);
accept();
}
void ScreenRecordingDialog::onCancel()
{
m_recAreaWidget->close();
reject();
}
} // namespace ScreenRecording
@@ -0,0 +1,102 @@
/**
* @RDE_HEADER@
* ScreenRecording - Dialog
* $Id$
* $HeadURL$
* $Author$
* Created by Filip Biedziak, NLE, 2020-06-23
*/
#pragma once
#include "screen_recording_worker.h"
#include <LtsWidgets/lts_dialog.h>
namespace Ui
{
class ScreenRecordingDialog;
} // namespace Ui
namespace LtsWidgets
{
class LtsBaseLayoutWidget;
}
namespace LtsSession
{
class LtsSession;
}
namespace NGL
{
class INGLLogger;
}
namespace ScreenRecording
{
class ScreenRecordingDialog final
: public LtsWidgets::LtsFramelessDialog
{
Q_OBJECT
public:
explicit ScreenRecordingDialog(const QString &menuGroup,
QWidget *parent = nullptr,
const LtsWidgets::Behaviour &behaviour = LtsWidgets::Behaviour::Modal);
~ScreenRecordingDialog();
// ------------------------------------------------------------------------------------------------
// PUBLIC MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
public:
/**
* @brief init initializes member variables with default values.
* @param session the current session
* @return true if the initialization was successfull, false otherwise
*/
bool init(LtsSession::LtsSession *session);
// ------------------------------------------------------------------------------------------------
// SIGNALS
// ------------------------------------------------------------------------------------------------
signals:
void startRecording(const ScreenRecordingWorker::RecordingData&);
// ------------------------------------------------------------------------------------------------
// PRIVATE SLOTS
// ------------------------------------------------------------------------------------------------
private slots:
/**
* @brief onBrowseRecordingFile opens a file dialog to choose the destination file.
*/
void onBrowseRecordingFile();
/**
* @brief onChangeRecordingArea updates the widget showing the area to record.
*/
void onChangeRecordingArea();
/**
* @brief onRecord collects the recording data from the GUI and emits the startRecording signal.
*/
void onRecord();
/**
* @brief onCancel closes the dialog.
*/
void onCancel();
// ------------------------------------------------------------------------------------------------
// PRIVATE MEMBER VARIABLES
// ------------------------------------------------------------------------------------------------
private:
Ui::ScreenRecordingDialog *m_ui{nullptr};
NGL::INGLLogger &m_logger;
LtsWidgets::LtsBaseLayoutWidget *m_baseLayoutPtr{nullptr};
QWidget *m_recAreaWidget{nullptr};
}; // ScreenRecordingDialog
} // namespace ScreenRecording
@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScreenRecordingDialog</class>
<widget class="QWidget" name="ScreenRecordingDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>429</width>
<height>271</height>
</rect>
</property>
<property name="windowTitle">
<string>Screen Recording</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QGroupBox" name="gbRecordingOptions">
<property name="title">
<string>Recording Options</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QComboBox" name="cobFrameRate"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lbxFrameRate">
<property name="text">
<string>Frame Rate:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cobVideoCodec"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbxVideoEncoder">
<property name="text">
<string>Video Encoder:</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="gbRecordingFile">
<property name="title">
<string>Recording File</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="lbxPath">
<property name="text">
<string>Path:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="leRecordingFile"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pbBrowseRecordingFile">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="LtsWidgets::LtsDialogButtonBox" name="ltsDialogButtonBox"/>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="gbRecordingScreen">
<property name="title">
<string>Recording Screen</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="lbxRecordingArea">
<property name="text">
<string>Recording Area:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cobRecordingArea"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="chbShowArea">
<property name="text">
<string>Show Recording Area</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="chbRecordTitleBar">
<property name="text">
<string>Record Title Bar</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LtsWidgets::LtsDialogButtonBox</class>
<extends>QWidget</extends>
<header>LtsWidgets/lts_dialog_button_box.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,285 @@
/**
* @RDE_HEADER@
* Screen Recording worker class for handling processes - Party
* $Id$
* $HeadURL$
* $Author$
* Created by Filip Biedziak, NLE, 2020-06-23
*/
#include "screen_recording_worker.h"
#include <cstdlib>
#include <LtsSession/lts_session.h>
#include <LtsWidgets/lts_base_layout_widget.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QtWidgets/QFileDialog>
#include <QThread>
#include <QProcess>
namespace ScreenRecording
{
#define EXECUTABLE QLatin1String("ffmpeg")
#define DISPLAY QLatin1String(std::getenv("DISPLAY")) + QLatin1String(".0+")
QStringList ScreenRecordingWorker::RecordingData::toStringList() const
{
QRect fixedRec = recArea;
if (videoCodec == VideoCodec::H264)
{
if (recArea.width() % 2 > 0)
fixedRec.setWidth(fixedRec.width() - 1);
if (recArea.height() % 2 > 0)
fixedRec.setHeight(fixedRec.height() - 1);
}
QStringList args = {QLatin1String("-y")};
args << QLatin1String("-f") << QLatin1String("x11grab");
args << QLatin1String("-s") << (QString::number(fixedRec.width()) + QLatin1String("x") + QString::number(fixedRec.height()));
args << QLatin1String("-r") << QString::number(static_cast<quint32>(frameRate));
args << QLatin1String("-i") << (DISPLAY + QString::number(fixedRec.x()) + QLatin1String(",") + QString::number(fixedRec.y()));
args << QLatin1String("-c:v") << videoCodecToArg(videoCodec);
// args << QLatin1String("-pix_fmt yuv420p");
if (videoCodec == VideoCodec::H264)
{
args << QLatin1String("-vf");
args << QLatin1String("format=yuv420p");
args << QLatin1String("-colorspace");
args << QLatin1String("1");
args << QLatin1String("-color_trc");
args << QLatin1String("1");
args << QLatin1String("-color_primaries");
args << QLatin1String("1");
}
//args << QLatin1String("-q:v") << QString::number(1);
args << filePath.filePath();
return args;
}
const QString ScreenRecordingWorker::frameRateFromToString(ScreenRecordingWorker::FrameRate frameRate)
{
switch (frameRate)
{
case ScreenRecordingWorker::FrameRate::VeryLow:
return tr("Very Low (%1 fps)").arg(static_cast<int>(ScreenRecordingWorker::FrameRate::VeryLow));
case ScreenRecordingWorker::FrameRate::Low:
return tr("Low (%1 fps)").arg(static_cast<int>(ScreenRecordingWorker::FrameRate::Low));
case ScreenRecordingWorker::FrameRate::Medium:
return tr("Medium (%1 fps)").arg(static_cast<int>(ScreenRecordingWorker::FrameRate::Medium));
case ScreenRecordingWorker::FrameRate::High:
return tr("High (%1 fps)").arg(static_cast<int>(ScreenRecordingWorker::FrameRate::High));
case ScreenRecordingWorker::FrameRate::VeryHigh:
return tr("Very High (%1 fps)").arg(static_cast<int>(ScreenRecordingWorker::FrameRate::VeryHigh));
default:
return tr("Error");
} // switch
}
const QString ScreenRecordingWorker::videoCodecToString(ScreenRecordingWorker::VideoCodec videoCodec)
{
switch (videoCodec)
{
case ScreenRecordingWorker::VideoCodec::H264:
return QLatin1String("H264");
case ScreenRecordingWorker::VideoCodec::FFv1:
return QLatin1String("FFmpeg video codec #1");
case ScreenRecordingWorker::VideoCodec::FFvHuff:
return QLatin1String("HuffYUV FFmpeg variant");
case ScreenRecordingWorker::VideoCodec::Huffyuv:
return QLatin1String("HuffYUV (Huffington YUV)");
case ScreenRecordingWorker::VideoCodec::Targa:
return QLatin1String("Truevision Targa image");
case ScreenRecordingWorker::VideoCodec::V210:
return QLatin1String("Uncompressed 4:2:2 10-bit");
case ScreenRecordingWorker::VideoCodec::ZLib:
return QLatin1String("LCL (Lossless Codec Library) ZLIB");
default:
return tr("Error");
} // switch
}
const QString ScreenRecordingWorker::videoCodecToArg(ScreenRecordingWorker::VideoCodec videoCodec)
{
switch (videoCodec)
{
case ScreenRecordingWorker::VideoCodec::H264:
return QLatin1String("libx264");
case ScreenRecordingWorker::VideoCodec::FFv1:
return QLatin1String("ffv1");
case ScreenRecordingWorker::VideoCodec::FFvHuff:
return QLatin1String("ffvhuff");
case ScreenRecordingWorker::VideoCodec::Huffyuv:
return QLatin1String("huffyuv");
case ScreenRecordingWorker::VideoCodec::Targa:
return QLatin1String("targa");
case ScreenRecordingWorker::VideoCodec::V210:
return QLatin1String("v210");
case ScreenRecordingWorker::VideoCodec::ZLib:
return QLatin1String("zlib");
default:
return QLatin1String("");
} // switch
}
const QString ScreenRecordingWorker::recordingAreaToString(ScreenRecordingWorker::RecordingArea recordingArea)
{
switch (recordingArea)
{
case ScreenRecordingWorker::RecordingArea::FullScreen:
return tr("Full Screen");
case ScreenRecordingWorker::RecordingArea::MainArea:
return tr("Main Area");
case ScreenRecordingWorker::RecordingArea::LeftOfMenu:
return tr("Left of Menu");
default:
return tr("Error");
} // switch
}
const QString ScreenRecordingWorker::s_recordingTmpDir = QDir::tempPath() + QLatin1String("/screenrecording");
const QString ScreenRecordingWorker::s_screenshotTmpDir = QDir::tempPath() + QLatin1String("/screenshot");
const QString ScreenRecordingWorker::s_videoFormat = QLatin1String("mp4");
const QStringList ScreenRecordingWorker::s_imageFormats = { QLatin1String("jpg"), QLatin1String("jepg"), QLatin1String("png") };
ScreenRecordingWorker::ScreenRecordingWorker(QObject *parent)
: QObject(parent)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("ScreenRecording"))
{
QDir tmpRecordingDir(s_recordingTmpDir);
if (!tmpRecordingDir.exists())
tmpRecordingDir.mkpath(QLatin1String("."));
QDir tmpScreenshotDir(s_screenshotTmpDir);
if (!tmpScreenshotDir.exists())
tmpScreenshotDir.mkpath(QLatin1String("."));
}
// ------------------------------------------------------------------------------------------------
// PUBLIC MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
bool ScreenRecordingWorker::takeScreenshot(const QFileInfo &fileInfo, LtsWidgets::LtsBaseLayoutWidget *baseLayout)
{
if (!s_imageFormats.contains(fileInfo.suffix()))
{
DEBUG_LOG(m_logger) << "file format not supported (must be .jpg, .jpeg or .png)";
return false;
}
QStringList args = {QLatin1String("-y")}; // overwrite files
args << QLatin1String("-f") << QLatin1String("x11grab");
args << QLatin1String("-s") << (QString::number(baseLayout->geometry().width()) + QLatin1String("x") + QString::number(baseLayout->geometry().height()));
args << QLatin1String("-r") << QString::number(1);
args << QLatin1String("-i") << (DISPLAY + QString::number(baseLayout->mapToGlobal({}).x()) + QLatin1String(",") + QString::number(baseLayout->mapToGlobal({}).y()));
args << QLatin1String("-vframes") << QString::number(1);
args << fileInfo.absoluteFilePath();
QProcess *proc = new QProcess();
proc->start(EXECUTABLE, args);
if (!proc->waitForStarted())
{
ERROR_LOG(m_logger) << QLatin1String("Could not start process '") + EXECUTABLE
+ QLatin1String("' with arguments '") + args.join(QLatin1String("', '")) + QLatin1String("'\n");
proc->deleteLater();
return false;
} // if
if (!proc->waitForFinished())
{
ERROR_LOG(m_logger) << QLatin1String("Could not finish process '") + EXECUTABLE
+ QLatin1String("' with arguments '") + args.join(QLatin1String("', '")) + QLatin1String("'\n");
proc->deleteLater();
return false;
} // if
return true;
}
bool ScreenRecordingWorker::stopRecording()
{
if (!m_isBusy)
{
DEBUG_LOG(m_logger) << "no running process";
return false;
}
m_recProc->terminate();
if (!m_recProc->waitForFinished(5000))
m_recProc->kill();
m_recProc->close();
m_isBusy = false;
return true;
}
// ------------------------------------------------------------------------------------------------
// PUBLIC SLOTS
// ------------------------------------------------------------------------------------------------
bool ScreenRecordingWorker::onStartRecording(const RecordingData &data)
{
if (m_isBusy)
{
DEBUG_LOG(m_logger) << "process is already running";
return false;
}
if (data.filePath.suffix() != s_videoFormat)
{
DEBUG_LOG(m_logger) << "file format not supported (must be .mp4)";
return false;
}
m_isBusy = true;
m_recProc = new QProcess();
m_recProc->start(EXECUTABLE, data.toStringList());
if (!m_recProc->waitForStarted())
{
ERROR_LOG(m_logger) << QLatin1String("Could not start process '") + EXECUTABLE
+ QLatin1String("' with arguments '") + data.toStringList().join(QLatin1String("', '")) + QLatin1String("'\n");
m_recProc->deleteLater();
m_isBusy = false;
return false;
} // if
return true;
}
#undef EXECUTABLE
#undef DISPLAY
} // namespace ScreenRecording
@@ -0,0 +1,184 @@
/**
* @RDE_HEADER@
* Screen Recording worker class for handling processes - Party
* $Id$
* $HeadURL$
* $Author$
* Created by Filip Biedziak, NLE, 2020-06-23
*/
#pragma once
#include <cstdlib>
#include <QFileInfo>
#include <QRect>
class QProcess;
namespace NGL
{
class INGLLogger;
}
namespace LtsWidgets
{
class LtsBaseLayoutWidget;
}
namespace LtsSession
{
class LtsSession;
}
namespace ScreenRecording
{
class ScreenRecordingWorker final
: public QObject
{
/** @brief ScreenRecordingWorker manages the screen capturing for both screen recording and
* taking a snapshot.
*
* FFmpeg is used via command line by executing it in a QProcess. Settings like available frame
* rates, possible video codecs or screen recording areas are specified.
*/
Q_OBJECT
public:
ScreenRecordingWorker(QObject *parent = nullptr);
enum class FrameRate { VeryLow = 1, Low = 5, Medium = 15, High = 25, VeryHigh = 30};
enum class RecordingArea { FullScreen, MainArea, LeftOfMenu };
enum class VideoCodec { FFv1, FFvHuff, Huffyuv, Targa, V210, ZLib, H264 };
static const QString s_recordingTmpDir;
static const QString s_screenshotTmpDir;
static const QString s_videoFormat;
static const QStringList s_imageFormats;
struct RecordingData
{
QFileInfo filePath{};
VideoCodec videoCodec{VideoCodec::FFv1};
QRect recArea{};
FrameRate frameRate{FrameRate::Medium};
QStringList toStringList() const;
};
// ------------------------------------------------------------------------------------------------
// PUBLIC STATIC MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
public:
/**
* @brief frameRateFromIndexToString generates a string to be displayed e.g. in a QComboBox.
* @param idx index of the frame rate in the enum
* @return a string to display
*/
static const QString frameRateFromToString(FrameRate frameRate);
/**
* @brief videoCodecToString generates a string to be displayed e.g. in a QComboBox
* @param videoCodec a video codec
* @return a string to display
*/
static const QString videoCodecToString(VideoCodec videoCodec);
/**
* @brief recordingAreaToString generates a string to be displayed e.g. in a QComboBox
* @param recordingArea a recording area
* @return a string to display
*/
static const QString recordingAreaToString(RecordingArea recordingArea);
/**
* @brief videoCodecToArg turns the video codec into a valid argument for FFmpeg.
* @param videoCodec a video codec
* @return the optional argument for FFmpeg associated with the codec
*/
static const QString videoCodecToArg(VideoCodec videoCodec);
// ------------------------------------------------------------------------------------------------
// PUBLIC MEMBER FUNCTIONS
// ------------------------------------------------------------------------------------------------
public:
/**
* @brief takeScreenshot creates a screenshot of the application.
*
* Both a file path and a pointer to the base layout widget must be provided. The latter is used
* to obtain the geometry of the application. Uses FFmpeg with a frame rate of 1 to take a
* screenshot.
* @param fileInfo file info with destination file path of the screenshot
* @param baseLayout pointer to the base layout
* @return true of the screenshot was taken, false otherwise
*/
bool takeScreenshot(const QFileInfo &fileInfo, LtsWidgets::LtsBaseLayoutWidget *baseLayout);
/**
* @brief onStopRecording terminates the running recording process.
* @return true if process has been stopped, false otherwise
*/
bool stopRecording();
inline bool isBusy() const { return m_isBusy; }
// ------------------------------------------------------------------------------------------------
// PUBLIC SLOTS
// ------------------------------------------------------------------------------------------------
public slots:
/**
* @brief onStartRecording initializes the screen recording using FFmpeg.
*
* The recording is started in a separate thread to ensure that it is running in the background.
* Optional arguments for FFmpeg are taken from the data struct containing the file path, the
* video codec, the frame rate and a QRect with the coordinates and dimensions of the recording
* area.
* @param data struct containing necessary recording information
* @return true if the screen is being recorded, false otherwise
*/
bool onStartRecording(const RecordingData &data);
// ------------------------------------------------------------------------------------------------
// PRIVATE MEMBER VARIABLES
// ------------------------------------------------------------------------------------------------
private:
NGL::INGLLogger &m_logger;
QProcess *m_recProc{nullptr};
bool m_isBusy{false};
};
static constexpr std::initializer_list<ScreenRecordingWorker::FrameRate> allFrameRates =
{
ScreenRecordingWorker::FrameRate::VeryLow,
ScreenRecordingWorker::FrameRate::Low,
ScreenRecordingWorker::FrameRate::Medium,
ScreenRecordingWorker::FrameRate::High,
ScreenRecordingWorker::FrameRate::VeryHigh
};
static constexpr std::initializer_list<ScreenRecordingWorker::RecordingArea> allRecordingAreas =
{
ScreenRecordingWorker::RecordingArea::FullScreen,
ScreenRecordingWorker::RecordingArea::MainArea,
ScreenRecordingWorker::RecordingArea::LeftOfMenu
};
static constexpr std::initializer_list<ScreenRecordingWorker::VideoCodec> allVideoCodecs =
{
ScreenRecordingWorker::VideoCodec::H264,
// ScreenRecordingWorker::VideoCodec::FFv1,
// ScreenRecordingWorker::VideoCodec::FFvHuff,
// ScreenRecordingWorker::VideoCodec::Huffyuv,
// ScreenRecordingWorker::VideoCodec::Targa,
// ScreenRecordingWorker::VideoCodec::V210,
// ScreenRecordingWorker::VideoCodec::ZLib
};
} // namespace ScreenRecording
Q_DECLARE_METATYPE(ScreenRecording::ScreenRecordingWorker::RecordingData)
Q_DECLARE_METATYPE(ScreenRecording::ScreenRecordingWorker::VideoCodec)
Q_DECLARE_METATYPE(ScreenRecording::ScreenRecordingWorker::FrameRate)
Q_DECLARE_METATYPE(ScreenRecording::ScreenRecordingWorker::RecordingArea)