Automatisierte Screenshot Aufnahme per [SCREENSHOT] datei implementiert
This commit is contained in:
@@ -7,7 +7,7 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
app.setApplicationName("UARTScope");
|
||||
app.setApplicationVersion("1.1.0");
|
||||
app.setApplicationVersion("1.2.0");
|
||||
app.setOrganizationName("ChicaDev");
|
||||
|
||||
app.setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QClipboard>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
@@ -35,6 +36,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
connect(m_worker, &SerialWorker::newLine, this, &MainWindow::onNewLine);
|
||||
connect(m_worker, &SerialWorker::tagDetected, this, &MainWindow::onTagDetected);
|
||||
connect(m_worker, &SerialWorker::screenshotRequested, this, &MainWindow::onScreenshotRequested);
|
||||
connect(m_worker, &SerialWorker::clearScreen, this, &MainWindow::onClearScreen);
|
||||
connect(m_worker, &SerialWorker::portOpened, this, &MainWindow::onPortOpened);
|
||||
connect(m_worker, &SerialWorker::portClosed, this, &MainWindow::onPortClosed);
|
||||
@@ -100,6 +102,30 @@ void MainWindow::setupUi()
|
||||
m_videoWidget = new VideoWidget(rightSplitter);
|
||||
m_videoWidget->setMinimumHeight(160);
|
||||
|
||||
connect(m_videoWidget, &VideoWidget::autoScreenshotResult, this,
|
||||
[this](bool ok, const QString &message) {
|
||||
statusBar()->showMessage(message, ok ? 4000 : 6000);
|
||||
// The status bar message disappears after a few seconds and is
|
||||
// easy to miss (especially right after triggering a capture
|
||||
// and looking at the video preview, not the status bar). Also
|
||||
// log it as a normal Raw View line - permanent, scrollable,
|
||||
// and sitting right next to the [SCREENSHOT] tag line itself
|
||||
// for context - so a failure (no folder configured, no frame
|
||||
// yet, etc.) is never silently lost.
|
||||
m_rawView->appendLine(
|
||||
QStringLiteral("[UARTSCOPE] %1").arg(message), m_suppressedTags);
|
||||
|
||||
// Also surface the outcome directly in the Tag Monitor's
|
||||
// [SCREENSHOT] panel - both its "Aktuell" tab (so the latest
|
||||
// status is visible at a glance) and its "Verlauf" history
|
||||
// (so every past trigger's outcome, success or failure, stays
|
||||
// reviewable with a timestamp). tagDetected() for the request
|
||||
// itself is emitted before screenshotRequested() in
|
||||
// SerialWorker, so this result entry always lands after the
|
||||
// corresponding request entry.
|
||||
m_tagWidget->handleTag(QStringLiteral("SCREENSHOT"), message);
|
||||
});
|
||||
|
||||
rightSplitter->addWidget(m_tagWidget);
|
||||
rightSplitter->addWidget(m_videoWidget);
|
||||
rightSplitter->setStretchFactor(0, 1);
|
||||
@@ -168,6 +194,12 @@ void MainWindow::setupToolBar()
|
||||
|
||||
tb->addSeparator();
|
||||
|
||||
m_screenshotDirAction = tb->addAction(tr("Screenshot folder…"));
|
||||
connect(m_screenshotDirAction, &QAction::triggered, this, &MainWindow::configureScreenshotDir);
|
||||
updateScreenshotDirTooltip();
|
||||
|
||||
tb->addSeparator();
|
||||
|
||||
auto *helpAction = tb->addAction(tr("Format reference"));
|
||||
helpAction->setToolTip(tr("Show UARTScope output format guide (copy for AI)"));
|
||||
connect(helpAction, &QAction::triggered, this, &MainWindow::showFormatReference);
|
||||
@@ -309,6 +341,12 @@ void MainWindow::onTagDetected(const QString &tag, const QString &value)
|
||||
m_tagWidget->handleTag(tag, value);
|
||||
}
|
||||
|
||||
void MainWindow::onScreenshotRequested(const QString &filename)
|
||||
{
|
||||
if (m_videoWidget)
|
||||
m_videoWidget->saveAutoScreenshot(filename);
|
||||
}
|
||||
|
||||
void MainWindow::clearAllViews()
|
||||
{
|
||||
m_rawView->clear();
|
||||
@@ -403,7 +441,33 @@ Every received line is appended with a hh:mm:ss.zzz timestamp.
|
||||
No special firmware changes needed.
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
5. COMPLETE EXAMPLE (Chica / Amiga hardware emulation)
|
||||
5. AUTOMATIC SCREENSHOT TRIGGER (V4L2 / HDMI grabber tab)
|
||||
────────────────────────────────────────────────────────────
|
||||
[SCREENSHOT] <filename> is a control tag: the instant this line is
|
||||
received, UARTScope saves whatever video frame is currently in the V4L2
|
||||
preview under <filename> - no dialog, no user reaction time needed. This
|
||||
is meant for catching a fast-changing signal (e.g. a memory dump flashed
|
||||
on an emulated display for only a moment) at the exact instant the
|
||||
firmware announces it, rather than relying on manually clicking the
|
||||
screenshot button and missing the window.
|
||||
|
||||
uart_printf("[SCREENSHOT] memdump_%lu.png\n", HAL_GetTick());
|
||||
|
||||
Notes:
|
||||
• Configure the target folder once via the toolbar
|
||||
("Screenshot folder…") - if unset, the trigger is reported but no
|
||||
file is written.
|
||||
• <filename> may omit the extension (defaults to .png); any directory
|
||||
components sent by the firmware are stripped for safety - the file
|
||||
always lands directly in the configured folder.
|
||||
• Reusing the same filename on every trigger is fine: UARTScope
|
||||
automatically appends _1, _2, … instead of overwriting.
|
||||
• The line is also still visible in the Tag Monitor's [SCREENSHOT]
|
||||
panel (with its own "Verlauf" history tab), so you get a timestamped
|
||||
log of every capture in addition to the saved image files.
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
6. COMPLETE EXAMPLE (Chica / Amiga hardware emulation)
|
||||
────────────────────────────────────────────────────────────
|
||||
void uart_status_update(void) {
|
||||
uart_printf("[WDG] uptime=%lu free=%lu load=%d temp=%d\n",
|
||||
@@ -421,6 +485,12 @@ void uart_status_update(void) {
|
||||
HAL_GetTick(), vcc_mv / 1000.0f, cpu_load_percent, core_temp_c);
|
||||
}
|
||||
|
||||
// Grab the exact video frame when a memory dump is about to be shown:
|
||||
void trigger_memdump_capture(void) {
|
||||
uart_printf("[SCREENSHOT] memdump_%lu.png\n", HAL_GetTick());
|
||||
show_memory_dump_on_screen();
|
||||
}
|
||||
|
||||
// Clear screen from firmware when you want a fresh start:
|
||||
// uart_printf("\033[2J\033[H");
|
||||
)UARTSCOPE";
|
||||
@@ -511,6 +581,35 @@ void MainWindow::configureTagFilter()
|
||||
dlg->deleteLater();
|
||||
}
|
||||
|
||||
// ── Auto-screenshot folder ──────────────────────────────────────────────────
|
||||
|
||||
void MainWindow::configureScreenshotDir()
|
||||
{
|
||||
const QString dir = QFileDialog::getExistingDirectory(
|
||||
this, tr("Auto-screenshot folder for [SCREENSHOT] tag"), m_screenshotDir);
|
||||
if (dir.isEmpty())
|
||||
return;
|
||||
|
||||
m_screenshotDir = dir;
|
||||
if (m_videoWidget)
|
||||
m_videoWidget->setAutoScreenshotDir(m_screenshotDir);
|
||||
updateScreenshotDirTooltip();
|
||||
|
||||
// Persist immediately, consistent with the tag filter dialog above.
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
void MainWindow::updateScreenshotDirTooltip()
|
||||
{
|
||||
if (!m_screenshotDirAction)
|
||||
return;
|
||||
m_screenshotDirAction->setToolTip(
|
||||
m_screenshotDir.isEmpty()
|
||||
? tr("No folder set yet - click to choose where [SCREENSHOT]-tag "
|
||||
"captures are saved")
|
||||
: tr("[SCREENSHOT] tag captures are saved to: %1").arg(m_screenshotDir));
|
||||
}
|
||||
|
||||
// ── About dialog ──────────────────────────────────────────────────────────
|
||||
|
||||
void MainWindow::showAbout()
|
||||
@@ -572,7 +671,7 @@ void MainWindow::showAbout()
|
||||
grid->addWidget(v, row, 1);
|
||||
};
|
||||
|
||||
addRow(0, tr("Version"), "1.1.0");
|
||||
addRow(0, tr("Version"), "1.2.0");
|
||||
addRow(1, tr("Entwickler"), "Dany Thinnes");
|
||||
addRow(2, tr("Projekt"), "Projekt Hirnfrei");
|
||||
addRow(3, tr("Framework"), QString("Qt %1").arg(QT_VERSION_STR));
|
||||
@@ -619,6 +718,8 @@ void MainWindow::saveSettings()
|
||||
tags.sort();
|
||||
s.setValue("tagfilter/suppressed", tags);
|
||||
|
||||
s.setValue("screenshot/autoDir", m_screenshotDir);
|
||||
|
||||
s.sync(); // flush to disk immediately (important since we also call
|
||||
// this mid-session from configureTagFilter())
|
||||
}
|
||||
@@ -647,4 +748,9 @@ void MainWindow::restoreSettings()
|
||||
for (const QString &t : tags)
|
||||
if (!t.isEmpty())
|
||||
m_suppressedTags.insert(t);
|
||||
|
||||
m_screenshotDir = s.value("screenshot/autoDir").toString();
|
||||
if (m_videoWidget)
|
||||
m_videoWidget->setAutoScreenshotDir(m_screenshotDir);
|
||||
updateScreenshotDirTooltip();
|
||||
}
|
||||
|
||||
@@ -392,7 +392,24 @@ void SerialWorker::processLine(const QString &line)
|
||||
if (match.hasMatch()) {
|
||||
const QString tag = match.captured(1).toUpper();
|
||||
const QString value = match.captured(2).trimmed();
|
||||
|
||||
// Emit tagDetected() first so the request itself (the filename the
|
||||
// firmware sent) lands in the Tag Monitor's [SCREENSHOT] history
|
||||
// before the save outcome does. Both signals are queued to the GUI
|
||||
// thread; queued events are processed in the order they were
|
||||
// posted, so emission order here directly determines the order
|
||||
// the two entries appear in - swap it and the result would show
|
||||
// up "before" the request that caused it.
|
||||
emit tagDetected(tag, value);
|
||||
|
||||
// [SCREENSHOT] <filename> is a control tag: fire immediately, as
|
||||
// close in time as possible to this line arriving, so the caller
|
||||
// can grab whichever V4L2 frame is currently in flight - e.g. to
|
||||
// capture the exact video frame showing a memory dump the moment
|
||||
// the firmware announces it, rather than relying on a human's
|
||||
// reaction time with the manual screenshot button.
|
||||
if (tag == QLatin1String("SCREENSHOT"))
|
||||
emit screenshotRequested(value);
|
||||
}
|
||||
|
||||
emit newLine(line);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QPainter>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include <QStandardPaths>
|
||||
@@ -164,6 +165,55 @@ void VideoWidget::onScreenshot()
|
||||
m_frame.save(path);
|
||||
}
|
||||
|
||||
void VideoWidget::saveAutoScreenshot(const QString &filename)
|
||||
{
|
||||
if (m_frame.isNull()) {
|
||||
emit autoScreenshotResult(false,
|
||||
tr("[SCREENSHOT] tag received, but no video frame is available yet."));
|
||||
return;
|
||||
}
|
||||
if (m_autoScreenshotDir.isEmpty()) {
|
||||
emit autoScreenshotResult(false,
|
||||
tr("[SCREENSHOT] tag received, but no auto-screenshot folder is "
|
||||
"configured (toolbar → \"Screenshot folder…\")."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Only take the bare filename from what the firmware sent - this
|
||||
// strips any directory components (incl. attempts at '../' path
|
||||
// traversal), so the firmware can never dictate where on disk we
|
||||
// write. Fall back to a timestamp if it sent nothing usable, and
|
||||
// default to .png if it didn't specify an extension.
|
||||
QString name = QFileInfo(filename.trimmed()).fileName();
|
||||
if (name.isEmpty()) {
|
||||
name = QStringLiteral("screenshot_%1.png")
|
||||
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss_zzz"));
|
||||
} else if (QFileInfo(name).suffix().isEmpty()) {
|
||||
name += QStringLiteral(".png");
|
||||
}
|
||||
|
||||
const QDir dir(m_autoScreenshotDir);
|
||||
QString path = dir.filePath(name);
|
||||
|
||||
// Don't silently overwrite an earlier capture if the firmware reuses
|
||||
// the same filename on every trigger (e.g. a generic "dump.png") -
|
||||
// append a numeric suffix instead so nothing gets lost.
|
||||
if (QFileInfo::exists(path)) {
|
||||
const QString base = QFileInfo(name).completeBaseName();
|
||||
const QString suffix = QFileInfo(name).suffix();
|
||||
int n = 1;
|
||||
do {
|
||||
path = dir.filePath(QStringLiteral("%1_%2.%3").arg(base).arg(n).arg(suffix));
|
||||
++n;
|
||||
} while (QFileInfo::exists(path));
|
||||
}
|
||||
|
||||
if (m_frame.save(path))
|
||||
emit autoScreenshotResult(true, tr("Screenshot saved: %1").arg(path));
|
||||
else
|
||||
emit autoScreenshotResult(false, tr("Failed to save screenshot to %1").arg(path));
|
||||
}
|
||||
|
||||
void VideoWidget::onNewFrame(const QImage &frame)
|
||||
{
|
||||
// Feed the recorder regardless of freeze state, so pausing the preview
|
||||
|
||||
Reference in New Issue
Block a user