diff --git a/CMakeLists.txt b/CMakeLists.txt index 872782c..0825b2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,8 @@ set(SOURCES src/tagwidget.cpp src/tagpanel.cpp src/connectdialog.cpp + src/v4l2worker.cpp + src/videowidget.cpp ) set(HEADERS @@ -33,9 +35,12 @@ set(HEADERS include/tagwidget.h include/tagpanel.h include/connectdialog.h + include/v4l2worker.h + include/videowidget.h ) -add_executable(uartscope ${SOURCES} ${HEADERS}) +qt6_add_resources(RESOURCES resources.qrc) +add_executable(uartscope ${SOURCES} ${HEADERS} ${RESOURCES}) target_include_directories(uartscope PRIVATE include) @@ -46,6 +51,10 @@ target_link_libraries(uartscope PRIVATE Qt6::SerialPort ) +# V4L2 uses Linux kernel headers directly (linux/videodev2.h) +# These are part of linux-api-headers / linux-libc-dev on all major distros. +# No additional library linkage needed. + # Installation install(TARGETS uartscope DESTINATION bin) @@ -56,3 +65,6 @@ configure_file( @ONLY ) install(FILES ${CMAKE_BINARY_DIR}/uartscope.desktop DESTINATION share/applications) +install(FILES ${CMAKE_SOURCE_DIR}/UartscopeLogo.png + DESTINATION share/icons/hicolor/512x512/apps + RENAME uartscope.png) diff --git a/README.md b/README.md index 6f325cb..bd2799e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # UARTScope -Ein moderner UART-Monitor für Linux mit Qt6-Oberfläche. Gebaut als Ersatz für minicom mit deutlich mehr Komfort – besonders nützlich bei der Entwicklung von Baremetal-Projekten wie [Chica](https://github.com/). +Ein moderner UART-Monitor für Linux mit Qt6-Oberfläche. Gebaut als vollwertiger Ersatz für minicom – besonders nützlich bei der Entwicklung von Baremetal-Projekten wie [Chica](https://git.projekt-hirnfrei.de/diabolus/chica) (Amiga-Hardware-Nachbildung auf dem Raspberry Pi). --- @@ -8,35 +8,45 @@ Ein moderner UART-Monitor für Linux mit Qt6-Oberfläche. Gebaut als Ersatz für | Feature | Beschreibung | |---|---| -| **Unbegrenzter Verlauf** | QPlainTextEdit mit unlimitierter Zeilenzahl – keine Daten gehen verloren | -| **Horizontal + vertikal scrollen** | Kein Zeilenumbruch, voller H-Scrollbalken | -| **Live-Logging** | Jede empfangene Zeile wird mit Zeitstempel in eine Datei geschrieben | -| **Tag-Monitor** | Zeilen mit `[TAG]` werden abgefangen und in eigenen Panels angezeigt | -| **Tabellenansicht** | CSV-formatierte UART-Zeilen werden in einer sortierbaren Tabelle dargestellt | +| **Unbegrenzter Verlauf** | Kein Zeilenlimit – keine Daten gehen verloren, egal wie viel der Pi sendet | +| **Timestamps** | Jede Zeile im Raw-View bekommt automatisch einen `hh:mm:ss.zzz`-Timestamp | +| **H + V Scrolling** | Kein Zeilenumbruch, voller horizontaler Scrollbalken | +| **Live-Logging** | Alle empfangenen Zeilen werden mit Timestamp in eine Datei geschrieben | +| **Tag-Monitor** | Zeilen mit `[TAG]` werden in eigenen Panels angezeigt und aktualisiert | +| **Tag-Filter** | Tags können aus dem Raw-View ausgeblendet werden (nur im Tag-Monitor sichtbar) | +| **Tabellenansicht** | CSV-formatierte UART-Zeilen werden in einer Tabelle dargestellt | | **Suche** | Inkrementelle Volltextsuche im Raw-View | -| **Dunkles Theme** | Fusion Dark – passt gut zur Terminal-Ästhetik | +| **Auto-Reconnect** | Bei Verbindungsabbruch wird automatisch neu verbunden (Intervall einstellbar) | +| **ANSI Clear-Screen** | `\033[2J\033[H` aus der Firmware leert alle Views gleichzeitig | +| **Copy-Buttons** | Raw-View und jedes Tag-Panel haben einen „📋 Copy"-Button | +| **V4L2 Live Video** | HDMI-Grabber direkt eingebunden – Live-Vorschau unter dem Tag-Monitor | +| **Screenshot** | Aktuellen Frame des HDMI-Grabbers als PNG/JPG speichern | +| **Format-Referenz** | Eingebauter Guide (mit Kopieren-Button) für KI-kompatible UART-Formatierung | +| **Fensterlayout** | Fenstergröße, Position und alle Splitter-Positionen werden beim Beenden gespeichert | --- ## Voraussetzungen ```bash +# Arch (empfohlen) +sudo pacman -S cmake qt6-base qt6-serialport + # Ubuntu / Debian sudo apt install cmake qt6-base-dev qt6-serialport-dev libqt6serialport6-dev -# Arch -sudo pacman -S cmake qt6-base qt6-serialport - # Fedora sudo dnf install cmake qt6-qtbase-devel qt6-qtserialport-devel ``` +V4L2 benötigt keine zusätzliche Library – `linux/videodev2.h` ist Teil der Standard-Kernel-Header. + --- ## Build ```bash -git clone +git clone https://git.projekt-hirnfrei.de/diabolus/uartscope.git cd uartscope cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc) @@ -50,59 +60,85 @@ sudo cmake --install build --- +## Berechtigungen + +```bash +# UART-Port +sudo usermod -aG dialout $USER + +# HDMI-Grabber / V4L2 +sudo usermod -aG video $USER + +# Einmal ausloggen / neu einloggen +``` + +Grabber testen: +```bash +v4l2-ctl --list-devices # alle /dev/video* Geräte +v4l2-ctl -d /dev/video0 --list-formats-ext # unterstützte Formate & Auflösungen +``` + +--- + ## UART-Ausgabe formatieren -### Tag-Monitor (`[TAG]`) +### Raw-View (immer aktiv) -Der Tag-Monitor fängt Zeilen ab, die ein Tag der Form `[TAGNAME]` enthalten. -Das Tag erscheint weiterhin im Raw-View (eingefärbt), wird aber **zusätzlich** im Tag-Panel aktualisiert. +Jede UART-Zeile erscheint im Raw-View mit Timestamp. Keine besondere Formatierung nötig. -**Watchdog-Beispiel** – dein Watchdog-Handler schickt: -``` -[WDG] uptime=12345 free_heap=43210 load=0.82 -``` -→ UARTScope erstellt automatisch ein Panel `[WDG]` mit einer Tabelle: - -| Key | Value | -|---|---| -| uptime | 12345 | -| free_heap | 43210 | -| load | 0.82 | - -Jedes Update blinkt kurz grün auf – du siehst sofort, dass neue Daten angekommen sind. - -**Mehrere Tags gleichzeitig:** +**Screen leeren** – von der Firmware aus alle Views gleichzeitig leeren: ```c -// In deinem Baremetal-Code: -uart_printf("[WDG] uptime=%lu free=%lu\n", uptime, heap_free); -uart_printf("[VIDEO] line=%d hblank=%d vblank=%d\n", line, hb, vb); -uart_printf("[AUDIO] buf=%d underruns=%d\n", audio_buf, underruns); +uart_printf("\033[2J\033[H"); ``` -Für jeden einzigartigen Tag wird automatisch ein eigenes Panel erstellt. + +### Tag-Monitor + +Zeilen mit `[TAGNAME]` werden im Tag-Monitor-Panel angezeigt **und** im Raw-View hervorgehoben (cyan). Über „Tag filter…" in der Toolbar können Tags vollständig aus dem Raw-View ausgeblendet werden, sodass sie nur noch im Tag-Monitor erscheinen. + +**Format:** `[TAGNAME] key1=value1 key2=value2 ...` + +- Tag-Name: Buchstaben, Ziffern, Underscore – z.B. `WDG`, `VIDEO`, `I2C_BUS` +- Key=Value-Paare: Leerzeichen-getrennt, Werte ohne Leerzeichen +- Ohne Key=Value-Paare wird der rohe String angezeigt + +**Beispiele:** +```c +uart_printf("[WDG] uptime=%lu free_heap=%lu temp=%d\n", + HAL_GetTick(), xPortGetFreeHeapSize(), core_temp); + +uart_printf("[VIDEO] line=%d hblank=%d vblank=%d copper=%d\n", + scanline, hblank_ticks, vblank_ticks, copper_dma); + +uart_printf("[AUDIO] ch=%d buf=%d underruns=%lu\n", + active_channels, buffer_fill, underruns); +``` + +Jeder einzigartige Tag bekommt automatisch ein eigenes Panel. Panels aktualisieren sich in-place – kein Scrollen nötig. ### Tabellenansicht -Die Tabellenansicht erwartet CSV-Zeilen. Optionale Header-Zeile mit `#`: +Optionale Header-Zeile mit `#`, dann CSV-Datenzeilen: ``` #time_ms,temperature,voltage,current 1000,23.5,3.30,0.42 2000,23.7,3.31,0.41 -3000,24.1,3.29,0.43 ``` -Delimiter ist per Dropdown umschaltbar (`,` `;` `\t` `|` `Space`). +Delimiter per Dropdown umschaltbar: `,` `;` `\t` `|` `Space` --- -## Serielle Ports – Berechtigungen +## Einstellungen -Auf den meisten Linux-Distros muss dein User in der Gruppe `dialout` sein: +Beim Beenden werden folgende Einstellungen automatisch gespeichert und beim nächsten Start wiederhergestellt: -```bash -sudo usermod -aG dialout $USER -# Einmal ausloggen / neu einloggen -``` +- Fenstergröße und -position +- Splitter-Positionen (Haupt-Splitter und Tag/Video-Splitter) +- Auto-Reconnect ein/aus und Intervall +- Tag-Filter (ausgeblendete Tags) + +Gespeichert unter `~/.config/ChicaDev/UARTScope.conf` (via `QSettings`). --- @@ -111,14 +147,18 @@ sudo usermod -aG dialout $USER ``` uartscope/ ├── CMakeLists.txt +├── README.md +├── uartscope.desktop.in ├── include/ -│ ├── mainwindow.h -│ ├── serialworker.h ← UART-Empfang im eigenen Thread -│ ├── rawview.h ← Unbegrenzter scrollbarer Log -│ ├── tableview.h ← CSV → QTableWidget +│ ├── mainwindow.h ← Hauptfenster, koordiniert alle Komponenten +│ ├── serialworker.h ← UART-Empfang im eigenen QThread, Auto-Reconnect +│ ├── rawview.h ← Unbegrenzter Log mit Timestamps, Suche, Copy +│ ├── tableview.h ← CSV-Parser → QTableWidget │ ├── tagwidget.h ← Container für Tag-Panels -│ ├── tagpanel.h ← Ein Panel pro [TAG] -│ └── connectdialog.h ← Port-Konfiguration +│ ├── tagpanel.h ← Ein Panel pro [TAG], Key=Value-Tabelle, Copy +│ ├── connectdialog.h ← Port-Konfiguration (Port, Baud, Log-Datei) +│ ├── v4l2worker.h ← V4L2-Capture in std::thread, MJPEG/YUYV/NV12 +│ └── videowidget.h ← Live-Vorschau, Freeze, Screenshot └── src/ ├── main.cpp ├── mainwindow.cpp @@ -127,15 +167,17 @@ uartscope/ ├── tableview.cpp ├── tagwidget.cpp ├── tagpanel.cpp - └── connectdialog.cpp + ├── connectdialog.cpp + ├── v4l2worker.cpp + └── videowidget.cpp ``` --- ## Erweiterungsideen -- **Protokoll-Filter**: Zeilen anhand von Regex ein-/ausblenden -- **Plot-Widget**: numerische Werte aus Tags live als Graph darstellen (Qt Charts) -- **UART senden**: TX-Eingabezeile hinzufügen für bidirektionale Kommunikation -- **Session-Replay**: gespeicherte Log-Dateien wieder abspielen +- **Plot-Widget**: numerische Tag-Werte live als Graph darstellen (Qt Charts) +- **UART senden**: TX-Eingabezeile für bidirektionale Kommunikation - **Hex-View**: rohe Bytes als Hex-Dump anzeigen +- **Session-Replay**: gespeicherte Log-Dateien abspielen +- **Regex-Filter**: Zeilen im Raw-View per regulärem Ausdruck ein-/ausblenden diff --git a/UartscopeLogo.png b/UartscopeLogo.png new file mode 100644 index 0000000..9da10c9 Binary files /dev/null and b/UartscopeLogo.png differ diff --git a/include/mainwindow.h b/include/mainwindow.h index 566c2f5..58d4df8 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -1,5 +1,8 @@ #pragma once #include +#include +#include +#include #include #include #include @@ -14,6 +17,7 @@ #include "rawview.h" #include "tableview.h" #include "tagwidget.h" +#include "videowidget.h" #include "connectdialog.h" #include @@ -37,21 +41,29 @@ private slots: void onTagDetected(const QString &tag, const QString &value); void showFormatReference(); void configureTagFilter(); + void showAbout(); + +protected: + void closeEvent(QCloseEvent *event) override; private: void setupUi(); void setupToolBar(); void setupStatusBar(); void clearAllViews(); + void doShutdown(); + void saveSettings(); + void restoreSettings(); // Worker & thread QThread *m_thread = nullptr; SerialWorker *m_worker = nullptr; // Views - RawView *m_rawView = nullptr; - TableView *m_tableView = nullptr; - TagWidget *m_tagWidget = nullptr; + RawView *m_rawView = nullptr; + TableView *m_tableView = nullptr; + TagWidget *m_tagWidget = nullptr; + VideoWidget *m_videoWidget= nullptr; // Status bar widgets QLabel *m_portLabel = nullptr; @@ -66,6 +78,11 @@ private: QAction *m_connectAction = nullptr; QAction *m_disconnectAction = nullptr; + // Splitters (saved/restored via QSettings) + QSplitter *m_mainSplitter = nullptr; + QSplitter *m_rightSplitter = nullptr; + SerialConfig m_lastConfig; QSet m_suppressedTags; // tags hidden from Raw view + bool m_shutdownDone = false; }; diff --git a/include/v4l2worker.h b/include/v4l2worker.h new file mode 100644 index 0000000..1f9891d --- /dev/null +++ b/include/v4l2worker.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include +#include + +class V4l2Worker : public QObject +{ + Q_OBJECT + +public: + explicit V4l2Worker(QObject *parent = nullptr); + ~V4l2Worker(); + +public slots: + void startCapture(const QString &device, int width = 1920, int height = 1080); + void stopCapture(); + +signals: + void newFrame(const QImage &frame); + void captureStarted(const QString &device, int width, int height, const QString &format); + void captureStopped(); + void errorOccurred(const QString &message); + +private: + void stop(); // joins m_captureThread, closes device – safe from any thread + void captureLoop(); // runs inside m_captureThread + bool openDevice(const QString &device, int width, int height); + void closeDevice(); + bool initMmap(); + void freeMmap(); + QImage convertToImage(const void *data, size_t size, int width, int height); + QImage yuyv2Rgb(const uchar *src, int width, int height); + QImage nv122Rgb(const uchar *src, int width, int height); + + int m_fd = -1; + int m_width = 1920; + int m_height = 1080; + uint32_t m_pixFmt = 0; + + static constexpr int MMAP_BUFFERS = 4; + struct MmapBuffer { void *start = nullptr; size_t length = 0; }; + MmapBuffer m_buffers[MMAP_BUFFERS]; + int m_nBuffers = 0; + + std::atomic m_running{false}; + std::thread m_captureThread; // dedicated capture thread – not the Qt event thread +}; diff --git a/include/videowidget.h b/include/videowidget.h new file mode 100644 index 0000000..1c73d2e --- /dev/null +++ b/include/videowidget.h @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v4l2worker.h" + +// VideoWidget shows a live preview of a V4L2 capture device. +// It lives in the right-side panel, below the Tag Monitor. +// Features: +// - Device selector (auto-enumerates /dev/video*) +// - Start / Stop capture +// - Freeze-frame toggle +// - Screenshot (saves current frame as PNG/JPG) +// - Frame is scaled to fit the widget while keeping aspect ratio +class VideoWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VideoWidget(QWidget *parent = nullptr); + ~VideoWidget(); + +public: + // Called by MainWindow::closeEvent to guarantee orderly shutdown before + // widget destruction. Safe to call multiple times. + void shutdown(); + +public slots: + void onNewFrame(const QImage &frame); + +private slots: + void onStartStop(); + void onFreeze(bool frozen); + void onScreenshot(); + void refreshDeviceList(); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + void setupUi(); + void startCapture(); + void stopCapture(); + void updateScaled(); + + // Worker + QThread *m_thread = nullptr; + V4l2Worker *m_worker = nullptr; + + // Current frame + QImage m_frame; + QImage m_scaled; + bool m_frozen = false; + bool m_running = false; + bool m_shutdownDone = false; + + // UI + QComboBox *m_deviceCombo = nullptr; + QPushButton *m_startBtn = nullptr; + QPushButton *m_freezeBtn = nullptr; + QPushButton *m_screenshotBtn= nullptr; + QLabel *m_infoLabel = nullptr; +}; diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..af7648e --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + UartscopeLogo.png + + diff --git a/src/main.cpp b/src/main.cpp index 389506e..47ed4f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,10 @@ int main(int argc, char *argv[]) darkPalette.setColor(QPalette::HighlightedText, Qt::white); app.setPalette(darkPalette); + // Set application icon (embedded via Qt resource) + const QIcon appIcon(":/icons/UartscopeLogo.png"); + app.setWindowIcon(appIcon); + MainWindow w; w.show(); return app.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ede276e..7f4a67a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,4 +1,5 @@ #include "mainwindow.h" +#include #include #include #include @@ -23,6 +24,7 @@ MainWindow::MainWindow(QWidget *parent) setupUi(); setupToolBar(); setupStatusBar(); + restoreSettings(); // ── Worker thread ───────────────────────────────────────────────────────── m_thread = new QThread(this); @@ -44,16 +46,48 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { - QMetaObject::invokeMethod(m_worker, "closePort", Qt::BlockingQueuedConnection); - m_thread->quit(); - m_thread->wait(3000); + // All real cleanup happens in closeEvent(). The destructor only runs if + // the window was destroyed without a close event (e.g. programmatically), + // so we guard against double-cleanup with m_shutdownDone. + doShutdown(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + doShutdown(); + event->accept(); +} + +void MainWindow::doShutdown() +{ + if (m_shutdownDone) + return; + m_shutdownDone = true; + + saveSettings(); + + // 1. Stop V4L2 capture first (joins the internal std::thread) + if (m_videoWidget) + m_videoWidget->shutdown(); + + // 2. Stop UART serial worker + if (m_thread && m_thread->isRunning()) { + QMetaObject::invokeMethod(m_worker, "closePort", Qt::BlockingQueuedConnection); + m_thread->quit(); + m_thread->wait(5000); + if (m_thread->isRunning()) + m_thread->terminate(); + } } void MainWindow::setupUi() { - auto *mainSplitter = new QSplitter(Qt::Horizontal, this); + // ── Outer horizontal splitter: (Raw+Table tabs) | right panel ──────────── + m_mainSplitter = new QSplitter(Qt::Horizontal, this); + auto *mainSplitter = m_mainSplitter; mainSplitter->setHandleWidth(5); + // Left: tabs for Raw and Table views auto *tabs = new QTabWidget(mainSplitter); tabs->setTabPosition(QTabWidget::South); @@ -63,12 +97,26 @@ void MainWindow::setupUi() m_tableView = new TableView(tabs); tabs->addTab(m_tableView, tr("Table view")); - m_tagWidget = new TagWidget(mainSplitter); - m_tagWidget->setMinimumWidth(220); - m_tagWidget->setMaximumWidth(420); + // Right: vertical splitter – Tag Monitor on top, Video below + m_rightSplitter = new QSplitter(Qt::Vertical, mainSplitter); + auto *rightSplitter = m_rightSplitter; + rightSplitter->setHandleWidth(5); + rightSplitter->setMinimumWidth(240); + rightSplitter->setMaximumWidth(480); + + m_tagWidget = new TagWidget(rightSplitter); + m_videoWidget = new VideoWidget(rightSplitter); + m_videoWidget->setMinimumHeight(160); + + rightSplitter->addWidget(m_tagWidget); + rightSplitter->addWidget(m_videoWidget); + // Initial split: ~30% tags, ~70% video – user can drag to taste + rightSplitter->setStretchFactor(0, 1); + rightSplitter->setStretchFactor(1, 2); + rightSplitter->setSizes({330, 330}); mainSplitter->addWidget(tabs); - mainSplitter->addWidget(m_tagWidget); + mainSplitter->addWidget(rightSplitter); mainSplitter->setStretchFactor(0, 3); mainSplitter->setStretchFactor(1, 1); @@ -135,6 +183,12 @@ void MainWindow::setupToolBar() 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); + + tb->addSeparator(); + + // ── About ───────────────────────────────────────────────────────────────── + auto *aboutAction = tb->addAction(tr("Über UARTScope")); + connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout); } void MainWindow::setupStatusBar() @@ -444,3 +498,151 @@ void MainWindow::configureTagFilter() } dlg->deleteLater(); } + +// ── Settings: save / restore ─────────────────────────────────────────────── + +void MainWindow::saveSettings() +{ + QSettings s("ChicaDev", "UARTScope"); + + // Window geometry & state + s.setValue("window/geometry", saveGeometry()); + s.setValue("window/state", saveState()); + + // Splitter sizes + if (m_mainSplitter) + s.setValue("splitter/main", m_mainSplitter->saveState()); + if (m_rightSplitter) + s.setValue("splitter/right", m_rightSplitter->saveState()); + + // Toolbar settings + s.setValue("reconnect/enabled", m_autoReconnectCb->isChecked()); + s.setValue("reconnect/interval", m_reconnectIntervalSb->value()); + + // Tag filter + QStringList tags(m_suppressedTags.values()); + tags.sort(); + s.setValue("tagfilter/suppressed", tags); +} + +void MainWindow::restoreSettings() +{ + QSettings s("ChicaDev", "UARTScope"); + + // Window geometry & state + if (s.contains("window/geometry")) + restoreGeometry(s.value("window/geometry").toByteArray()); + if (s.contains("window/state")) + restoreState(s.value("window/state").toByteArray()); + + // Splitter sizes (only restore if we have saved data) + if (m_mainSplitter && s.contains("splitter/main")) + m_mainSplitter->restoreState(s.value("splitter/main").toByteArray()); + if (m_rightSplitter && s.contains("splitter/right")) + m_rightSplitter->restoreState(s.value("splitter/right").toByteArray()); + + // Toolbar settings + if (s.contains("reconnect/enabled")) + m_autoReconnectCb->setChecked(s.value("reconnect/enabled").toBool()); + if (s.contains("reconnect/interval")) + m_reconnectIntervalSb->setValue(s.value("reconnect/interval").toInt()); + + // Tag filter + const QStringList tags = s.value("tagfilter/suppressed").toStringList(); + m_suppressedTags.clear(); + for (const QString &t : tags) + if (!t.isEmpty()) + m_suppressedTags.insert(t); +} + +// ── About dialog ────────────────────────────────────────────────────────── + +void MainWindow::showAbout() +{ + auto *dlg = new QDialog(this); + dlg->setWindowTitle(tr("Über UARTScope")); + dlg->setFixedSize(400, 280); + + auto *root = new QVBoxLayout(dlg); + root->setContentsMargins(0, 0, 0, 0); + root->setSpacing(0); + + // ── Header block (dark accent) ──────────────────────────────────────────── + auto *header = new QWidget(dlg); + header->setFixedHeight(110); + header->setStyleSheet("background-color: #1e1e1e;"); + auto *headerLayout = new QHBoxLayout(header); + headerLayout->setContentsMargins(20, 14, 24, 14); + headerLayout->setSpacing(16); + + // Logo + auto *logoLabel = new QLabel(header); + const QPixmap logoPix(":/icons/UartscopeLogo.png"); + logoLabel->setPixmap(logoPix.scaled(72, 72, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + logoLabel->setStyleSheet("background: transparent;"); + logoLabel->setFixedSize(72, 72); + + // Title + subtitle + auto *titleBlock = new QVBoxLayout(); + titleBlock->setSpacing(4); + auto *titleLabel = new QLabel("UARTScope", header); + titleLabel->setStyleSheet( + "color: #ffffff; font-size: 26px; font-weight: bold; background: transparent;"); + auto *subtitleLabel = new QLabel(tr("UART-Monitor & HDMI-Viewer für Baremetal-Entwicklung"), header); + subtitleLabel->setStyleSheet( + "color: #56b6c2; font-size: 11px; background: transparent;"); + titleBlock->addWidget(titleLabel); + titleBlock->addWidget(subtitleLabel); + + headerLayout->addWidget(logoLabel); + headerLayout->addLayout(titleBlock); + root->addWidget(header); + + // ── Info grid ───────────────────────────────────────────────────────────── + auto *body = new QWidget(dlg); + body->setStyleSheet("background-color: #252526;"); + auto *grid = new QGridLayout(body); + grid->setContentsMargins(24, 20, 24, 20); + grid->setVerticalSpacing(10); + grid->setHorizontalSpacing(16); + grid->setColumnMinimumWidth(0, 90); + + const QString labelStyle = "color: #888888; font-weight: bold; background: transparent;"; + const QString valueStyle = "color: #d4d4d4; background: transparent;"; + + auto addRow = [&](int row, const QString &label, const QString &value) { + auto *l = new QLabel(label, body); + auto *v = new QLabel(value, body); + l->setStyleSheet(labelStyle); + v->setStyleSheet(valueStyle); + l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + grid->addWidget(l, row, 0); + grid->addWidget(v, row, 1); + }; + + addRow(0, tr("Version"), "1.0.0"); + addRow(1, tr("Entwickler"), "Dany Thinnes"); + addRow(2, tr("Projekt"), "Projekt Hirnfrei"); + addRow(3, tr("Framework"), QString("Qt %1").arg(QT_VERSION_STR)); + addRow(4, tr("Sprache"), "C++17"); + + root->addWidget(body, 1); + + // ── Footer with close button ────────────────────────────────────────────── + auto *footer = new QWidget(dlg); + footer->setStyleSheet("background-color: #252526; border-top: 1px solid #3c3c3c;"); + auto *footerLayout = new QHBoxLayout(footer); + footerLayout->setContentsMargins(16, 10, 16, 10); + + auto *closeBtn = new QPushButton(tr("Schließen"), footer); + closeBtn->setDefault(true); + closeBtn->setMinimumWidth(90); + connect(closeBtn, &QPushButton::clicked, dlg, &QDialog::accept); + + footerLayout->addStretch(); + footerLayout->addWidget(closeBtn); + root->addWidget(footer); + + dlg->exec(); + dlg->deleteLater(); +} diff --git a/src/v4l2worker.cpp b/src/v4l2worker.cpp new file mode 100644 index 0000000..9c38061 --- /dev/null +++ b/src/v4l2worker.cpp @@ -0,0 +1,288 @@ +#include "v4l2worker.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static int xioctl(int fd, unsigned long req, void *arg) +{ + int r; + do { r = ::ioctl(fd, req, arg); } while (r == -1 && errno == EINTR); + return r; +} + +V4l2Worker::V4l2Worker(QObject *parent) + : QObject(parent) +{} + +V4l2Worker::~V4l2Worker() +{ + stop(); +} + +// ── public slots ────────────────────────────────────────────────────────────── + +void V4l2Worker::startCapture(const QString &device, int width, int height) +{ + if (m_running.load()) + stop(); + + if (!openDevice(device, width, height)) + return; + + m_running.store(true); + + // Run the blocking capture loop in a plain std::thread so the Qt event + // loop of this worker object stays responsive to stopCapture() signals. + m_captureThread = std::thread([this] { captureLoop(); }); +} + +void V4l2Worker::stopCapture() +{ + stop(); +} + +// ── internal stop (safe to call from any thread) ────────────────────────────── + +void V4l2Worker::stop() +{ + m_running.store(false); + if (m_captureThread.joinable()) + m_captureThread.join(); // wait for captureLoop() to exit + closeDevice(); +} + +// ── capture loop (runs in m_captureThread) ──────────────────────────────────── + +void V4l2Worker::captureLoop() +{ + v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(m_fd, VIDIOC_STREAMON, &type) == -1) { + emit errorOccurred(tr("VIDIOC_STREAMON failed: %1").arg(strerror(errno))); + m_running.store(false); + return; + } + + while (m_running.load()) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + timeval tv{0, 100000}; // 100 ms timeout → checks m_running 10x/sec + const int r = ::select(m_fd + 1, &fds, nullptr, nullptr, &tv); + if (r == 0) continue; + if (r == -1) { + if (errno == EINTR) continue; + emit errorOccurred(tr("select() failed: %1").arg(strerror(errno))); + break; + } + + v4l2_buffer buf{}; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + if (xioctl(m_fd, VIDIOC_DQBUF, &buf) == -1) { + if (errno == EAGAIN) continue; + emit errorOccurred(tr("VIDIOC_DQBUF failed: %1").arg(strerror(errno))); + break; + } + + const QImage img = convertToImage( + m_buffers[buf.index].start, buf.bytesused, m_width, m_height); + if (!img.isNull()) + emit newFrame(img); + + if (xioctl(m_fd, VIDIOC_QBUF, &buf) == -1) { + emit errorOccurred(tr("VIDIOC_QBUF failed: %1").arg(strerror(errno))); + break; + } + } + + xioctl(m_fd, VIDIOC_STREAMOFF, &type); + emit captureStopped(); +} + +// ── device open / close ─────────────────────────────────────────────────────── + +bool V4l2Worker::openDevice(const QString &device, int width, int height) +{ + m_width = width; + m_height = height; + + m_fd = ::open(device.toLocal8Bit().constData(), O_RDWR | O_NONBLOCK); + if (m_fd == -1) { + emit errorOccurred(tr("Cannot open %1: %2").arg(device, strerror(errno))); + return false; + } + + v4l2_capability cap{}; + if (xioctl(m_fd, VIDIOC_QUERYCAP, &cap) == -1) { + emit errorOccurred(tr("VIDIOC_QUERYCAP failed: %1").arg(strerror(errno))); + closeDevice(); + return false; + } + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + emit errorOccurred(tr("%1 is not a video capture device").arg(device)); + closeDevice(); + return false; + } + + const uint32_t preferred[] = { + V4L2_PIX_FMT_MJPEG, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_NV12, + 0 + }; + + m_pixFmt = 0; + for (int i = 0; preferred[i]; ++i) { + v4l2_format fmt{}; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = static_cast(width); + fmt.fmt.pix.height = static_cast(height); + fmt.fmt.pix.pixelformat = preferred[i]; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (xioctl(m_fd, VIDIOC_S_FMT, &fmt) == 0) { + m_pixFmt = fmt.fmt.pix.pixelformat; + m_width = static_cast(fmt.fmt.pix.width); + m_height = static_cast(fmt.fmt.pix.height); + break; + } + } + if (!m_pixFmt) { + emit errorOccurred(tr("No supported pixel format found on %1").arg(device)); + closeDevice(); + return false; + } + + QString fmtName; + switch (m_pixFmt) { + case V4L2_PIX_FMT_MJPEG: fmtName = "MJPEG"; break; + case V4L2_PIX_FMT_YUYV: fmtName = "YUYV"; break; + case V4L2_PIX_FMT_NV12: fmtName = "NV12"; break; + default: fmtName = "?"; break; + } + + if (!initMmap()) { + closeDevice(); + return false; + } + + emit captureStarted(device, m_width, m_height, fmtName); + return true; +} + +void V4l2Worker::closeDevice() +{ + freeMmap(); + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +} + +bool V4l2Worker::initMmap() +{ + v4l2_requestbuffers req{}; + req.count = MMAP_BUFFERS; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + if (xioctl(m_fd, VIDIOC_REQBUFS, &req) == -1) { + emit errorOccurred(tr("VIDIOC_REQBUFS failed: %1").arg(strerror(errno))); + return false; + } + m_nBuffers = static_cast(req.count); + + for (int i = 0; i < m_nBuffers; ++i) { + v4l2_buffer buf{}; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = static_cast(i); + if (xioctl(m_fd, VIDIOC_QUERYBUF, &buf) == -1) { + emit errorOccurred(tr("VIDIOC_QUERYBUF failed: %1").arg(strerror(errno))); + return false; + } + m_buffers[i].length = buf.length; + m_buffers[i].start = ::mmap(nullptr, buf.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + m_fd, buf.m.offset); + if (m_buffers[i].start == MAP_FAILED) { + emit errorOccurred(tr("mmap failed: %1").arg(strerror(errno))); + return false; + } + if (xioctl(m_fd, VIDIOC_QBUF, &buf) == -1) { + emit errorOccurred(tr("VIDIOC_QBUF failed: %1").arg(strerror(errno))); + return false; + } + } + return true; +} + +void V4l2Worker::freeMmap() +{ + for (int i = 0; i < m_nBuffers; ++i) { + if (m_buffers[i].start && m_buffers[i].start != MAP_FAILED) { + ::munmap(m_buffers[i].start, m_buffers[i].length); + m_buffers[i].start = nullptr; + } + } + m_nBuffers = 0; +} + +QImage V4l2Worker::convertToImage(const void *data, size_t size, int width, int height) +{ + switch (m_pixFmt) { + case V4L2_PIX_FMT_MJPEG: { + QByteArray ba(static_cast(data), static_cast(size)); + QBuffer buf(&ba); + QImageReader reader(&buf, "JPEG"); + return reader.read(); + } + case V4L2_PIX_FMT_YUYV: + return yuyv2Rgb(static_cast(data), width, height); + case V4L2_PIX_FMT_NV12: + return nv122Rgb(static_cast(data), width, height); + default: + return {}; + } +} + +QImage V4l2Worker::yuyv2Rgb(const uchar *src, int width, int height) +{ + QImage img(width, height, QImage::Format_RGB32); + for (int y = 0; y < height; ++y) { + const uchar *row = src + y * width * 2; + QRgb *dst = reinterpret_cast(img.scanLine(y)); + for (int x = 0; x < width; x += 2) { + const int Y0 = row[0], U = row[1]-128, Y1 = row[2], V = row[3]-128; + row += 4; + auto clamp = [](int v) { return static_cast(v<0?0:v>255?255:v); }; + dst[x] = qRgb(clamp(Y0+1.402f*V), clamp(Y0-0.344f*U-0.714f*V), clamp(Y0+1.772f*U)); + dst[x+1] = qRgb(clamp(Y1+1.402f*V), clamp(Y1-0.344f*U-0.714f*V), clamp(Y1+1.772f*U)); + } + } + return img; +} + +QImage V4l2Worker::nv122Rgb(const uchar *src, int width, int height) +{ + QImage img(width, height, QImage::Format_RGB32); + const uchar *uvPlane = src + width * height; + auto clamp = [](int v) { return static_cast(v<0?0:v>255?255:v); }; + for (int y = 0; y < height; ++y) { + const uchar *yRow = src + y * width; + const uchar *uvRow = uvPlane + (y/2) * width; + QRgb *dst = reinterpret_cast(img.scanLine(y)); + for (int x = 0; x < width; ++x) { + const int Y = yRow[x], U = uvRow[x&~1]-128, V = uvRow[(x&~1)+1]-128; + dst[x] = qRgb(clamp(Y+1.402f*V), clamp(Y-0.344f*U-0.714f*V), clamp(Y+1.772f*U)); + } + } + return img; +} diff --git a/src/videowidget.cpp b/src/videowidget.cpp new file mode 100644 index 0000000..79a3a61 --- /dev/null +++ b/src/videowidget.cpp @@ -0,0 +1,260 @@ +#include "videowidget.h" + +#include +#include +#include +#include +#include +#include +#include + +// Enumerate /dev/video* entries +static QStringList enumerateVideoDevices() +{ + QStringList devices; + const QDir dev("/dev"); + const auto entries = dev.entryList({"video*"}, QDir::System, QDir::Name); + for (const auto &e : entries) + devices << "/dev/" + e; + return devices; +} + +// ── ctor / dtor ─────────────────────────────────────────────────────────────── + +VideoWidget::VideoWidget(QWidget *parent) + : QWidget(parent) +{ + setupUi(); + + m_thread = new QThread(this); + m_worker = new V4l2Worker(); + m_worker->moveToThread(m_thread); + + connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater); + + connect(m_worker, &V4l2Worker::newFrame, this, &VideoWidget::onNewFrame, + Qt::QueuedConnection); + connect(m_worker, &V4l2Worker::captureStarted, this, [this](const QString &dev, int w, int h, const QString &fmt) { + m_infoLabel->setText(tr("%1 %2×%3 %4").arg(dev).arg(w).arg(h).arg(fmt)); + m_running = true; + m_startBtn->setText(tr("■ Stop")); + }); + connect(m_worker, &V4l2Worker::captureStopped, this, [this] { + m_running = false; + m_startBtn->setText(tr("▶ Start")); + m_infoLabel->setText(tr("Stopped")); + update(); + }); + connect(m_worker, &V4l2Worker::errorOccurred, this, [this](const QString &msg) { + m_infoLabel->setText(tr("Error: %1").arg(msg)); + m_running = false; + m_startBtn->setText(tr("▶ Start")); + }); + + m_thread->start(); +} + +void VideoWidget::shutdown() +{ + if (m_shutdownDone) + return; + m_shutdownDone = true; + + // stopCapture() sets m_running=false and joins the std::thread internally. + // Must happen before we quit the Qt event thread. + m_worker->stopCapture(); + + if (m_thread && m_thread->isRunning()) { + m_thread->quit(); + m_thread->wait(5000); + if (m_thread->isRunning()) + m_thread->terminate(); + } +} + +VideoWidget::~VideoWidget() +{ + shutdown(); +} + +// ── UI setup ───────────────────────────────────────────────────────────────── + +void VideoWidget::setupUi() +{ + setMinimumHeight(140); + + auto *layout = new QVBoxLayout(this); + layout->setContentsMargins(2, 2, 2, 2); + layout->setSpacing(3); + + // ── Controls row ────────────────────────────────────────────────────────── + auto *ctrlRow = new QHBoxLayout(); + ctrlRow->setSpacing(4); + + m_deviceCombo = new QComboBox(this); + m_deviceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + refreshDeviceList(); + + auto *refreshBtn = new QPushButton("↻", this); + refreshBtn->setMaximumWidth(26); + refreshBtn->setToolTip(tr("Refresh device list")); + connect(refreshBtn, &QPushButton::clicked, this, &VideoWidget::refreshDeviceList); + + m_startBtn = new QPushButton(tr("▶ Start"), this); + m_startBtn->setMaximumWidth(70); + connect(m_startBtn, &QPushButton::clicked, this, &VideoWidget::onStartStop); + + m_freezeBtn = new QPushButton(tr("❚❚ Freeze"), this); + m_freezeBtn->setCheckable(true); + m_freezeBtn->setMaximumWidth(80); + connect(m_freezeBtn, &QPushButton::toggled, this, &VideoWidget::onFreeze); + + m_screenshotBtn = new QPushButton(tr("📷"), this); + m_screenshotBtn->setMaximumWidth(32); + m_screenshotBtn->setToolTip(tr("Save screenshot (PNG/JPG)")); + connect(m_screenshotBtn, &QPushButton::clicked, this, &VideoWidget::onScreenshot); + + ctrlRow->addWidget(m_deviceCombo, 1); + ctrlRow->addWidget(refreshBtn); + ctrlRow->addWidget(m_startBtn); + ctrlRow->addWidget(m_freezeBtn); + ctrlRow->addWidget(m_screenshotBtn); + layout->addLayout(ctrlRow); + + // ── Info label ──────────────────────────────────────────────────────────── + m_infoLabel = new QLabel(tr("No device started"), this); + m_infoLabel->setStyleSheet("color: gray; font-size: 9px;"); + layout->addWidget(m_infoLabel); + + // The video frame is drawn directly in paintEvent – no QLabel needed. + // We leave stretch space for it. + layout->addStretch(1); +} + +// ── Slots ───────────────────────────────────────────────────────────────────── + +void VideoWidget::onStartStop() +{ + if (m_running) + stopCapture(); + else + startCapture(); +} + +void VideoWidget::onFreeze(bool frozen) +{ + m_frozen = frozen; + m_freezeBtn->setText(frozen ? tr("▶ Live") : tr("❚❚ Freeze")); +} + +void VideoWidget::onScreenshot() +{ + if (m_frame.isNull()) + return; + + const QString defaultName = + QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + + "/uartscope_" + + QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + + ".png"; + + const QString path = QFileDialog::getSaveFileName( + this, tr("Save screenshot"), defaultName, + tr("PNG Image (*.png);;JPEG Image (*.jpg *.jpeg)")); + + if (!path.isEmpty()) + m_frame.save(path); +} + +void VideoWidget::onNewFrame(const QImage &frame) +{ + if (m_frozen) + return; + m_frame = frame; + updateScaled(); + update(); // trigger paintEvent +} + +void VideoWidget::refreshDeviceList() +{ + const QString current = m_deviceCombo->currentText(); + m_deviceCombo->clear(); + const auto devices = enumerateVideoDevices(); + for (const auto &d : devices) + m_deviceCombo->addItem(d); + // Restore previous selection if still present + const int idx = m_deviceCombo->findText(current); + if (idx >= 0) + m_deviceCombo->setCurrentIndex(idx); +} + +// ── Capture start/stop ──────────────────────────────────────────────────────── + +void VideoWidget::startCapture() +{ + const QString dev = m_deviceCombo->currentText(); + if (dev.isEmpty()) + return; + m_frame = QImage(); + m_scaled = QImage(); + // startCapture blocks in the worker thread until stopCapture() is called + QMetaObject::invokeMethod(m_worker, "startCapture", + Qt::QueuedConnection, + Q_ARG(QString, dev), + Q_ARG(int, 1920), + Q_ARG(int, 1080)); +} + +void VideoWidget::stopCapture() +{ + QMetaObject::invokeMethod(m_worker, "stopCapture", Qt::QueuedConnection); +} + +// ── Paint ───────────────────────────────────────────────────────────────────── + +void VideoWidget::updateScaled() +{ + // Video area = widget height minus the two control rows (~55 px) + const QRect videoRect(0, 55, width(), height() - 55); + if (videoRect.width() < 10 || videoRect.height() < 10 || m_frame.isNull()) + return; + + m_scaled = m_frame.scaled(videoRect.size(), Qt::KeepAspectRatio, + Qt::SmoothTransformation); +} + +void VideoWidget::paintEvent(QPaintEvent *) +{ + QPainter p(this); + + // Background + p.fillRect(rect(), QColor(0x10, 0x10, 0x10)); + + if (m_scaled.isNull()) { + p.setPen(QColor(0x44, 0x44, 0x44)); + p.drawText(rect(), Qt::AlignCenter, + m_running ? tr("Waiting for frames…") : tr("No signal")); + return; + } + + // Centre the scaled frame in the video area + const QRect videoRect(0, 55, width(), height() - 55); + const QPoint origin( + videoRect.left() + (videoRect.width() - m_scaled.width()) / 2, + videoRect.top() + (videoRect.height() - m_scaled.height()) / 2); + p.drawImage(origin, m_scaled); + + // Frozen overlay + if (m_frozen) { + p.setPen(QColor(0xff, 0xcc, 0x00)); + p.setFont(QFont("Sans", 9, QFont::Bold)); + p.drawText(videoRect.adjusted(4, 4, -4, -4), + Qt::AlignTop | Qt::AlignRight, tr("FREEZE")); + } +} + +void VideoWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + updateScaled(); +} diff --git a/uartscope-git/PKGBUILD b/uartscope-git/PKGBUILD index 7b21008..0595ee7 100644 --- a/uartscope-git/PKGBUILD +++ b/uartscope-git/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: diabolus pkgname=uartscope -pkgver=1.0.0.r0.gcc102c9 +pkgver=1.0.0.r1.g2181f25 pkgrel=1 pkgdesc="Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect" arch=('x86_64' 'aarch64') diff --git a/uartscope-git/pkg/uartscope/.BUILDINFO b/uartscope-git/pkg/uartscope/.BUILDINFO index 6da4f23..a5eff86 100644 --- a/uartscope-git/pkg/uartscope/.BUILDINFO +++ b/uartscope-git/pkg/uartscope/.BUILDINFO @@ -1,11 +1,11 @@ format = 2 pkgname = uartscope pkgbase = uartscope -pkgver = 1.0.0.r0.gcc102c9-1 +pkgver = 1.0.0.r1.g2181f25-1 pkgarch = x86_64 -pkgbuild_sha256sum = b55d7f4f6a11dd33fc6e311f94ea98ca2ad601fa044e110b56af77a00ec7cdbc +pkgbuild_sha256sum = 367d5f02c03027788d532bdeda7084f79008b9dcf902393b209ad7f2ae43859c packager = Unknown Packager -builddate = 1780954815 +builddate = 1780988717 builddir = /home/diabolus/Arbeit/Projekt-Hirnfrei/uartscope/uartscope-git startdir = /home/diabolus/Arbeit/Projekt-Hirnfrei/uartscope/uartscope-git buildtool = makepkg @@ -287,7 +287,7 @@ installed = dhcpcd-10.3.2-1-x86_64 installed = dialog-1:1.3_20260107-1-x86_64 installed = diffutils-3.12-2-x86_64 installed = ding-libs-0.7.0-1-x86_64 -installed = discord-1:1.0.141-1-x86_64 +installed = discord-1:1.0.142-1-x86_64 installed = discount-3.0.1.2-1-x86_64 installed = djvulibre-3.5.30-1-x86_64 installed = dkms-3.4.1-1-any @@ -399,7 +399,7 @@ installed = freeglut-3.8.0-1-x86_64 installed = freeoffice-1234-1-x86_64 installed = freerdp-2:3.26.0-1-x86_64 installed = freetype2-2.14.3-1-x86_64 -installed = frei0r-plugins-3.1.3-1-x86_64 +installed = frei0r-plugins-3.2.0-1-x86_64 installed = fribidi-1.0.16-2-x86_64 installed = fritzing-1.0.7-2-x86_64 installed = fs-uae-3.2.35-2-x86_64 @@ -525,7 +525,7 @@ installed = go-tools-4:0.45.0-1-x86_64 installed = gobject-introspection-1.86.0-2-x86_64 installed = gobject-introspection-runtime-1.86.0-2-x86_64 installed = gom-0.5.6-1-x86_64 -installed = google-chrome-149.0.7827.53-1-x86_64 +installed = google-chrome-149.0.7827.102-1-x86_64 installed = google-earth-pro-7.3.7.1155-1-x86_64 installed = google-glog-0.7.1-2-x86_64 installed = goverlay-1.8.2-1-x86_64 @@ -814,7 +814,7 @@ installed = kservice5-5.116.0-3-x86_64 installed = ksmtp-26.04.2-1-x86_64 installed = kstatusnotifieritem-6.26.0-2-x86_64 installed = ksvg-6.26.0-2-x86_64 -installed = ksystemstats-6.6.5-1-x86_64 +installed = ksystemstats-6.6.5-2-x86_64 installed = ktextaddons-2.0.2-1-x86_64 installed = ktexteditor-6.26.0-1-x86_64 installed = ktexttemplate-6.26.0-2-x86_64 @@ -829,7 +829,7 @@ installed = kwayland-6.6.5-1-x86_64 installed = kwayland5-5.116.0-2-x86_64 installed = kwidgetsaddons-6.26.0-1-x86_64 installed = kwidgetsaddons5-5.116.0-2-x86_64 -installed = kwin-6.6.5-3-x86_64 +installed = kwin-6.6.5-4-x86_64 installed = kwindowsystem-6.26.0-2-x86_64 installed = kwindowsystem5-5.116.0-2-x86_64 installed = kxmlgui-6.26.0-1-x86_64 @@ -992,7 +992,7 @@ installed = lib32-pipewire-jack-1:1.6.6-1-x86_64 installed = lib32-pixman-0.46.4-1-x86_64 installed = lib32-popt-1.19-2-x86_64 installed = lib32-rust-libs-1:1.96.0-1-x86_64 -installed = lib32-sdl2-compat-2.32.68-1-x86_64 +installed = lib32-sdl2-compat-2.32.70-1-x86_64 installed = lib32-sdl3-3.4.10-1-x86_64 installed = lib32-speex-1.2.1-2-x86_64 installed = lib32-spirv-tools-1:1.4.350.0-1-x86_64 @@ -1184,7 +1184,7 @@ installed = libkleo-26.04.2-1-x86_64 installed = libksba-1.8.0-1-x86_64 installed = libkscreen-6.6.5-1-x86_64 installed = libksieve-26.04.2-1-x86_64 -installed = libksysguard-6.6.5-1-x86_64 +installed = libksysguard-6.6.5-2-x86_64 installed = liblc3-1.1.3-2-x86_64 installed = libldac-2.0.2.3-3-x86_64 installed = libldap-2.6.13-1-x86_64 @@ -1939,7 +1939,7 @@ installed = python-dbus-1.4.0-2-x86_64 installed = python-decorator-5.3.1-1-any installed = python-defusedxml-0.7.1-8-any installed = python-dill-0.4.1-1-any -installed = python-distlib-0.4.1-1-any +installed = python-distlib-0.4.2-1-any installed = python-distro-1.9.0-4-any installed = python-distutils-extra-2.39-15-any installed = python-django-5.2.13-1-any @@ -2121,7 +2121,7 @@ installed = python-smartypants-2.0.2-2-any installed = python-sniffio-1.3.1-5-any installed = python-snowballstemmer-3.1.1-1-any installed = python-sortedcontainers-2.4.0-8-any -installed = python-soundfile-0.13.1-1-any +installed = python-soundfile-0.14.0-1-any installed = python-soupsieve-2.8.4-1-any installed = python-soxr-1.1.0-2-x86_64 installed = python-sphinx-9.1.0-1-any @@ -2169,7 +2169,7 @@ installed = python-uv-build-0.11.19-1-x86_64 installed = python-vcs-versioning-1.1.1-1-any installed = python-vdf-4.0-5-any installed = python-virtualenv-21.4.2-1-any -installed = python-wcwidth-0.8.0-1-any +installed = python-wcwidth-0.8.1-1-any installed = python-webencodings-0.5.1-13-any installed = python-websocket-client-1.9.0-3-any installed = python-websockets-16.0-1-x86_64 @@ -2432,7 +2432,7 @@ installed = scummvm-2026.2.0-1-x86_64 installed = sddm-0.21.0-7-x86_64 installed = sddm-sugar-candy-git-1.6r42.d31dbf5-1-any installed = sdl12-compat-1.2.68-2-x86_64 -installed = sdl2-compat-2.32.68-1-x86_64 +installed = sdl2-compat-2.32.70-1-x86_64 installed = sdl2_image-2.8.12-1-x86_64 installed = sdl2_mixer-2.8.2-1-x86_64 installed = sdl2_net-2:2.4.0-1-x86_64 @@ -2790,6 +2790,7 @@ installed = ttf-ubuntu-font-family-1:0.83-2-any installed = tumbler-4.20.1-1-x86_64 installed = twolame-0.4.0-4-x86_64 installed = tzdata-2026b-1-x86_64 +installed = uartscope-1.0.0.r0.gcc102c9-1-x86_64 installed = uchardet-0.0.8-4-x86_64 installed = udisks2-2.11.1-2-x86_64 installed = unifdef-2.12-4-x86_64 @@ -2828,7 +2829,7 @@ installed = virtualbox-7.2.8-2-x86_64 installed = virtualbox-guest-iso-7.2.8-1-any installed = virtualbox-host-dkms-7.2.8-2-x86_64 installed = virtualgl-3.1.4-1-x86_64 -installed = visual-studio-code-bin-1.123.0-5-x86_64 +installed = visual-studio-code-bin-1.123.0-6-x86_64 installed = vivaldi-8.0.4033.44-1-x86_64 installed = vivaldi-ffmpeg-codecs-148.0.7778.256-1-x86_64 installed = vlc-3.0.23_2-6-x86_64 diff --git a/uartscope-git/pkg/uartscope/.MTREE b/uartscope-git/pkg/uartscope/.MTREE index e32b1bc..1d33d2f 100644 Binary files a/uartscope-git/pkg/uartscope/.MTREE and b/uartscope-git/pkg/uartscope/.MTREE differ diff --git a/uartscope-git/pkg/uartscope/.PKGINFO b/uartscope-git/pkg/uartscope/.PKGINFO index c09b7ae..e91017d 100644 --- a/uartscope-git/pkg/uartscope/.PKGINFO +++ b/uartscope-git/pkg/uartscope/.PKGINFO @@ -3,12 +3,12 @@ pkgname = uartscope pkgbase = uartscope xdata = pkgtype=pkg -pkgver = 1.0.0.r0.gcc102c9-1 +pkgver = 1.0.0.r1.g2181f25-1 pkgdesc = Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect url = https://git.projekt-hirnfrei.de/diabolus/uartscope -builddate = 1780954815 +builddate = 1780988717 packager = Unknown Packager -size = 176146 +size = 209170 arch = x86_64 license = MIT conflict = uartscope diff --git a/uartscope-git/pkg/uartscope/usr/bin/uartscope b/uartscope-git/pkg/uartscope/usr/bin/uartscope index 4202da1..b21e59b 100755 Binary files a/uartscope-git/pkg/uartscope/usr/bin/uartscope and b/uartscope-git/pkg/uartscope/usr/bin/uartscope differ diff --git a/uartscope-git/src/uartscope b/uartscope-git/src/uartscope index cc102c9..2181f25 160000 --- a/uartscope-git/src/uartscope +++ b/uartscope-git/src/uartscope @@ -1 +1 @@ -Subproject commit cc102c93eb17f7b910d8e74c3505f198bed77f10 +Subproject commit 2181f254d29960cffe3b1f6158a97b3c65fb0d15 diff --git a/uartscope-git/uartscope/FETCH_HEAD b/uartscope-git/uartscope/FETCH_HEAD index 427d222..fcc6eb0 100644 --- a/uartscope-git/uartscope/FETCH_HEAD +++ b/uartscope-git/uartscope/FETCH_HEAD @@ -1,2 +1,2 @@ -cc102c93eb17f7b910d8e74c3505f198bed77f10 not-for-merge branch 'main' of https://git.projekt-hirnfrei.de/diabolus/uartscope +2181f254d29960cffe3b1f6158a97b3c65fb0d15 not-for-merge branch 'main' of https://git.projekt-hirnfrei.de/diabolus/uartscope cc102c93eb17f7b910d8e74c3505f198bed77f10 not-for-merge tag 'v1.0.0' of https://git.projekt-hirnfrei.de/diabolus/uartscope diff --git a/uartscope.desktop.in b/uartscope.desktop.in index 5c42a21..d66b061 100644 --- a/uartscope.desktop.in +++ b/uartscope.desktop.in @@ -2,7 +2,7 @@ Name=UARTScope Comment=UART Serial Monitor Exec=uartscope -Icon=utilities-terminal +Icon=uartscope Terminal=false Type=Application Categories=Development;Utility;