diff --git a/CMakeLists.txt b/CMakeLists.txt index 0825b2a..22ca2cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,6 @@ target_link_libraries(uartscope PRIVATE # Installation install(TARGETS uartscope DESTINATION bin) -# Optional: create a .desktop file for Linux app launchers configure_file( ${CMAKE_SOURCE_DIR}/uartscope.desktop.in ${CMAKE_BINARY_DIR}/uartscope.desktop diff --git a/README.md b/README.md index bd2799e..915a80d 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,21 @@ Ein moderner UART-Monitor für Linux mit Qt6-Oberfläche. Gebaut als vollwertige |---|---| | **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 | +| **Auto-Scroll** | Standardmäßig aktiv – springt automatisch ans Ende neuer Ausgaben | | **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) | +| **Listen-Werte** | `;`-getrennte Werte in einem Tag werden als mehrzeilige Liste dargestellt | +| **Tag-Filter** | Tags können aus dem Raw-View ausgeblendet werden – wird dauerhaft gespeichert | | **Tabellenansicht** | CSV-formatierte UART-Zeilen werden in einer Tabelle dargestellt | | **Suche** | Inkrementelle Volltextsuche im Raw-View | | **Auto-Reconnect** | Bei Verbindungsabbruch wird automatisch neu verbunden (Intervall einstellbar) | -| **ANSI Clear-Screen** | `\033[2J\033[H` aus der Firmware leert alle Views gleichzeitig | +| **ANSI Clear-Screen** | `\033[2J\033[H` aus der Firmware leert Raw-View, Tabelle, Tag-Monitor und Video-Vorschau 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 | +| **Fensterlayout** | Fenstergröße, Position, Splitter-Positionen und Tag-Filter werden beim Beenden gespeichert | --- @@ -86,34 +88,48 @@ v4l2-ctl -d /dev/video0 --list-formats-ext # unterstützte Formate & Auflösung Jede UART-Zeile erscheint im Raw-View mit Timestamp. Keine besondere Formatierung nötig. -**Screen leeren** – von der Firmware aus alle Views gleichzeitig leeren: +**Screen leeren** – von der Firmware aus Raw-View, Tabelle, Tag-Monitor und Video-Vorschau gleichzeitig leeren: ```c uart_printf("\033[2J\033[H"); ``` ### 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. +Zeilen mit `[TAGNAME]` werden im Tag-Monitor-Panel angezeigt **und** im Raw-View hervorgehoben (cyan), sofern der Tag nicht über den Tag-Filter ausgeblendet wurde. **Format:** `[TAGNAME] key1=value1 key2=value2 ...` -- Tag-Name: Buchstaben, Ziffern, Underscore – z.B. `WDG`, `VIDEO`, `I2C_BUS` +- Tag-Name: Buchstaben, Ziffern, Underscore – z.B. `WDG`, `VIDEO`, `KICKSTART` - Key=Value-Paare: Leerzeichen-getrennt, Werte ohne Leerzeichen - Ohne Key=Value-Paare wird der rohe String angezeigt +**Listen-Werte:** Ein Wert mit `;`-getrennten Einträgen wird als mehrzeilige Liste unter dem Key dargestellt: + +```c +uart_printf("[KICKSTART] Devs=trackdisk.device;input.device\n"); +``` + +Im Tag-Monitor erscheint das als: + +``` +Devs trackdisk.device + input.device +``` + +Jeder weitere Eintrag bekommt seine eigene Tabellenzeile, der Key wird nur einmal angezeigt. + **Beispiele:** ```c uart_printf("[WDG] uptime=%lu free_heap=%lu temp=%d\n", HAL_GetTick(), xPortGetFreeHeapSize(), core_temp); +uart_printf("[KICKSTART] Libs=graphics.library;intuition.library;dos.library\n"); + 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. +Jeder einzigartige Tag bekommt automatisch ein eigenes Panel. Panels aktualisieren sich in-place – kein Scrollen nötig. Listen-Längen können sich von Update zu Update ändern, die Tabelle passt sich automatisch an. ### Tabellenansicht @@ -129,6 +145,12 @@ Delimiter per Dropdown umschaltbar: `,` `;` `\t` `|` `Space` --- +## Tag-Filter + +Über „Tag filter…" in der Toolbar lassen sich Tags aus dem Raw-View ausblenden, sodass sie nur noch im Tag-Monitor erscheinen. Ein Tag pro Zeile, ohne Klammern (z.B. `WDG`). Groß-/Kleinschreibung spielt keine Rolle. Der Filter wird sofort beim Schließen des Dialogs gespeichert und beim nächsten Programmstart automatisch wiederhergestellt. + +--- + ## Einstellungen Beim Beenden werden folgende Einstellungen automatisch gespeichert und beim nächsten Start wiederhergestellt: @@ -148,14 +170,16 @@ Gespeichert unter `~/.config/ChicaDev/UARTScope.conf` (via `QSettings`). uartscope/ ├── CMakeLists.txt ├── README.md +├── resources.qrc +├── UartscopeLogo.png ├── uartscope.desktop.in ├── include/ │ ├── mainwindow.h ← Hauptfenster, koordiniert alle Komponenten -│ ├── serialworker.h ← UART-Empfang im eigenen QThread, Auto-Reconnect +│ ├── serialworker.h ← UART-Empfang im eigenen QThread, Auto-Reconnect, ANSI-Erkennung │ ├── 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], Key=Value-Tabelle, Copy +│ ├── tagpanel.h ← Ein Panel pro [TAG], Key=Value-Tabelle mit Listen-Support, 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 diff --git a/include/connectdialog.h b/include/connectdialog.h index 18ec6c6..14eba37 100644 --- a/include/connectdialog.h +++ b/include/connectdialog.h @@ -14,7 +14,7 @@ struct SerialConfig { QSerialPort::Parity parity = QSerialPort::NoParity; QSerialPort::StopBits stopBits = QSerialPort::OneStop; QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl; - QString logFilePath; // empty = no logging + QString logFilePath; }; class ConnectDialog : public QDialog @@ -23,7 +23,6 @@ class ConnectDialog : public QDialog public: explicit ConnectDialog(QWidget *parent = nullptr); - SerialConfig config() const; private slots: diff --git a/include/mainwindow.h b/include/mainwindow.h index 58d4df8..5a086db 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -5,13 +5,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include "serialworker.h" #include "rawview.h" @@ -19,7 +19,6 @@ #include "tagwidget.h" #include "videowidget.h" #include "connectdialog.h" -#include class MainWindow : public QMainWindow { @@ -29,6 +28,9 @@ public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); +protected: + void closeEvent(QCloseEvent *event) override; + private slots: void onConnectClicked(); void onDisconnectClicked(); @@ -43,9 +45,6 @@ private slots: void configureTagFilter(); void showAbout(); -protected: - void closeEvent(QCloseEvent *event) override; - private: void setupUi(); void setupToolBar(); @@ -55,34 +54,29 @@ private: 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; VideoWidget *m_videoWidget= nullptr; - // Status bar widgets - QLabel *m_portLabel = nullptr; - QLabel *m_stateLabel = nullptr; - QLabel *m_reconnectLabel = nullptr; + QLabel *m_portLabel = nullptr; + QLabel *m_stateLabel = nullptr; + QLabel *m_reconnectLabel = nullptr; - // Toolbar widgets - QCheckBox *m_autoReconnectCb = nullptr; - QSpinBox *m_reconnectIntervalSb= nullptr; + QCheckBox *m_autoReconnectCb = nullptr; + QSpinBox *m_reconnectIntervalSb = nullptr; - // Actions QAction *m_connectAction = nullptr; QAction *m_disconnectAction = nullptr; // Splitters (saved/restored via QSettings) - QSplitter *m_mainSplitter = nullptr; - QSplitter *m_rightSplitter = nullptr; + QSplitter *m_mainSplitter = nullptr; + QSplitter *m_rightSplitter = nullptr; - SerialConfig m_lastConfig; - QSet m_suppressedTags; // tags hidden from Raw view - bool m_shutdownDone = false; + SerialConfig m_lastConfig; + QSet m_suppressedTags; // tags hidden from Raw view + bool m_shutdownDone = false; }; diff --git a/include/rawview.h b/include/rawview.h index 9795531..883048a 100644 --- a/include/rawview.h +++ b/include/rawview.h @@ -7,14 +7,12 @@ #include #include #include -#include +#include #include -// RawView shows the raw UART output in a QPlainTextEdit with: -// - Timestamp prefix on every line (hh:mm:ss.zzz) -// - Configurable tag suppression (lines whose tag is in the filter set are hidden) -// - Unlimited history, H+V scrolling, incremental search -// - Copy-to-clipboard and Clear buttons +// RawView shows the raw UART output with timestamps, search, tag suppression, +// unlimited history, H+V scrolling and a copy-to-clipboard button. +// Auto-scroll is ON by default. class RawView : public QWidget { Q_OBJECT @@ -23,7 +21,6 @@ public: explicit RawView(QWidget *parent = nullptr); public slots: - // suppressedTags: set of uppercase tag names whose lines should NOT appear here void appendLine(const QString &line, const QSet &suppressedTags = {}); void clear(); @@ -43,7 +40,7 @@ private: QPushButton *m_copyBtn = nullptr; QCheckBox *m_autoScrollCb = nullptr; QLabel *m_lineCountLbl = nullptr; - bool m_autoScroll = true; + bool m_autoScroll = true; // ON by default int m_lineCount = 0; static const QRegularExpression s_tagRe; diff --git a/include/serialworker.h b/include/serialworker.h index 58e6044..b46aaad 100644 --- a/include/serialworker.h +++ b/include/serialworker.h @@ -10,9 +10,9 @@ // SerialWorker lives in its own QThread and owns the QSerialPort. // It emits newLine() for every complete line received, // tagDetected() when a line contains a recognised tag like [WDG], -// and clearScreen() when the ANSI clear-screen sequence \033[2J is received. -// Auto-reconnect: if the port drops unexpectedly (e.g. USB cable pulled), -// the worker retries every reconnectIntervalMs until success or closePort(). +// and clearScreen() when an ANSI clear-screen sequence is received. +// Auto-reconnect: if the port drops unexpectedly, the worker retries +// every reconnectIntervalMs until success or closePort(). class SerialWorker : public QObject { Q_OBJECT @@ -21,9 +21,8 @@ public: explicit SerialWorker(QObject *parent = nullptr); ~SerialWorker(); - // Auto-reconnect configuration (call before openPort, or any time) - void setAutoReconnect(bool enabled) { m_autoReconnect = enabled; } - void setReconnectInterval(int ms) { m_reconnectIntervalMs = ms; } + void setAutoReconnect(bool enabled) { m_autoReconnect = enabled; } + void setReconnectInterval(int ms) { m_reconnectIntervalMs = ms; } public slots: void openPort(const QString &portName, qint32 baudRate, @@ -31,17 +30,17 @@ public slots: QSerialPort::Parity parity, QSerialPort::StopBits stopBits, QSerialPort::FlowControl flowControl); - void closePort(); // explicit user disconnect – stops reconnect + void closePort(); void setLogFile(const QString &path); void stopLogging(); signals: void newLine(const QString &line); void tagDetected(const QString &tag, const QString &value); - void clearScreen(); // emitted when \033[2J\033[H (or \033[2J alone) is received + void clearScreen(); void portOpened(); void portClosed(); - void reconnecting(int attempt); // fired each retry attempt + void reconnecting(int attempt); void errorOccurred(const QString &message); private slots: @@ -54,23 +53,22 @@ private: void processLine(const QString &line); void scheduleReconnect(); - QSerialPort *m_port = nullptr; - QFile *m_logFile = nullptr; + QSerialPort *m_port = nullptr; + QFile *m_logFile = nullptr; QTextStream *m_logStream = nullptr; QString m_buffer; + QByteArray m_scanTail; // carries partial ANSI sequences across reads - // Reconnect state - QTimer *m_reconnectTimer = nullptr; - bool m_autoReconnect = true; - bool m_userDisconnected = false; // true only after explicit closePort() - int m_reconnectIntervalMs = 2000; - int m_reconnectAttempt = 0; + QTimer *m_reconnectTimer = nullptr; + bool m_autoReconnect = true; + bool m_userDisconnected = false; + int m_reconnectIntervalMs = 2000; + int m_reconnectAttempt = 0; - // Saved config for reconnect QString m_portName; - qint32 m_baudRate = 115200; - QSerialPort::DataBits m_dataBits = QSerialPort::Data8; - QSerialPort::Parity m_parity = QSerialPort::NoParity; - QSerialPort::StopBits m_stopBits = QSerialPort::OneStop; - QSerialPort::FlowControl m_flowControl= QSerialPort::NoFlowControl; + qint32 m_baudRate = 115200; + QSerialPort::DataBits m_dataBits = QSerialPort::Data8; + QSerialPort::Parity m_parity = QSerialPort::NoParity; + QSerialPort::StopBits m_stopBits = QSerialPort::OneStop; + QSerialPort::FlowControl m_flowControl = QSerialPort::NoFlowControl; }; diff --git a/include/tableview.h b/include/tableview.h index 67a3221..01ec428 100644 --- a/include/tableview.h +++ b/include/tableview.h @@ -10,13 +10,6 @@ #include // TableView interprets UART lines as delimited data and shows them in a table. -// The first line that matches the column count is used as the header row if it -// starts with '#'. Otherwise column names are auto-generated (Col 1, Col 2, …). -// -// Supported delimiters: comma, semicolon, tab, pipe, space -// To use: send lines like: -// #time,temp,voltage,current -// 1234,23.5,3.3,0.42 class TableView : public QWidget { Q_OBJECT @@ -37,16 +30,16 @@ private: void parseHeaders(const QString &line); bool tryAppendRow(const QString &line); - QTableWidget *m_table = nullptr; - QComboBox *m_delimCombo = nullptr; - QLineEdit *m_maxRowsEdit = nullptr; - QPushButton *m_clearBtn = nullptr; - QCheckBox *m_autoScrollCb = nullptr; + QTableWidget *m_table = nullptr; + QComboBox *m_delimCombo = nullptr; + QLineEdit *m_maxRowsEdit = nullptr; + QPushButton *m_clearBtn = nullptr; + QCheckBox *m_autoScrollCb = nullptr; QStringList m_headers; - QList m_rows; // raw data for rebuild - int m_maxRows = 500; - char m_delimiter = ','; + QList m_rows; + int m_maxRows = 500; + char m_delimiter = ','; bool m_autoScroll = true; bool m_headersSet = false; }; diff --git a/include/tagpanel.h b/include/tagpanel.h index ccbd421..2a9d68d 100644 --- a/include/tagpanel.h +++ b/include/tagpanel.h @@ -6,11 +6,16 @@ #include #include #include +#include // TagPanel displays the most recent data for a single tag (e.g. [WDG]). // Key=value pairs are shown in a table that updates in-place. -// A "Copy" button copies the current values to the clipboard. -// No timestamp is shown here – timestamps belong in the Raw view. +// If a value contains ';', it is treated as a LIST: the key is shown once +// and each list element gets its own row below it (key cell left empty +// for the continuation rows, like a merged-cell look). +// Example: Devs=trackdisk.device;input.device +// Devs trackdisk.device +// input.device class TagPanel : public QGroupBox { Q_OBJECT @@ -29,10 +34,16 @@ private slots: private: void parseKeyValue(const QString &value); void showRaw(const QString &value); - void ensureRow(const QString &key); + // Replaces all rows belonging to `key` with one row per value in `values`. + void setKeyValues(const QString &key, const QStringList &values); QString m_tag; QTableWidget *m_table = nullptr; QLabel *m_rawLabel = nullptr; - QStringList m_keys; + + // Track which table rows belong to which key, so repeated updates can + // replace the previous rows instead of appending duplicates. + // Maps key -> (first row index, row count) + struct KeyRange { int firstRow; int count; }; + QMap m_keyRanges; }; diff --git a/include/tagwidget.h b/include/tagwidget.h index 0f6f368..bc50f96 100644 --- a/include/tagwidget.h +++ b/include/tagwidget.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -25,6 +25,6 @@ public slots: void removeTag(const QString &tag); private: - QVBoxLayout *m_panelLayout = nullptr; + QVBoxLayout *m_panelLayout = nullptr; QMap m_panels; }; diff --git a/include/v4l2worker.h b/include/v4l2worker.h index 1f9891d..6b190a5 100644 --- a/include/v4l2worker.h +++ b/include/v4l2worker.h @@ -24,8 +24,8 @@ signals: void errorOccurred(const QString &message); private: - void stop(); // joins m_captureThread, closes device – safe from any thread - void captureLoop(); // runs inside m_captureThread + void stop(); + void captureLoop(); bool openDevice(const QString &device, int width, int height); void closeDevice(); bool initMmap(); @@ -45,5 +45,5 @@ private: int m_nBuffers = 0; std::atomic m_running{false}; - std::thread m_captureThread; // dedicated capture thread – not the Qt event thread + std::thread m_captureThread; }; diff --git a/include/videowidget.h b/include/videowidget.h index 1c73d2e..3606caa 100644 --- a/include/videowidget.h +++ b/include/videowidget.h @@ -7,18 +7,9 @@ #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 @@ -28,12 +19,11 @@ public: ~VideoWidget(); public: - // Called by MainWindow::closeEvent to guarantee orderly shutdown before - // widget destruction. Safe to call multiple times. - void shutdown(); + void shutdown(); // safe to call multiple times, called from MainWindow::closeEvent public slots: void onNewFrame(const QImage &frame); + void clearFrame(); // blanks the preview (used on ANSI clear-screen) private slots: void onStartStop(); @@ -51,21 +41,18 @@ private: 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; + QComboBox *m_deviceCombo = nullptr; + QPushButton *m_startBtn = nullptr; + QPushButton *m_freezeBtn = nullptr; + QPushButton *m_screenshotBtn = nullptr; + QLabel *m_infoLabel = nullptr; }; diff --git a/src/connectdialog.cpp b/src/connectdialog.cpp index cc52c81..cf41d2c 100644 --- a/src/connectdialog.cpp +++ b/src/connectdialog.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -16,19 +15,16 @@ ConnectDialog::ConnectDialog(QWidget *parent) form->setRowWrapPolicy(QFormLayout::DontWrapRows); form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); - // ── Port ────────────────────────────────────────────────────────────────── auto *portRow = new QHBoxLayout(); m_portCombo = new QComboBox(this); m_portCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); auto *refreshBtn = new QPushButton(tr("↻"), this); refreshBtn->setMaximumWidth(30); - refreshBtn->setToolTip(tr("Refresh port list")); connect(refreshBtn, &QPushButton::clicked, this, &ConnectDialog::refreshPorts); portRow->addWidget(m_portCombo, 1); portRow->addWidget(refreshBtn); form->addRow(tr("Port:"), portRow); - // ── Baud rate ───────────────────────────────────────────────────────────── m_baudCombo = new QComboBox(this); const QList bauds = {1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600}; @@ -37,7 +33,6 @@ ConnectDialog::ConnectDialog(QWidget *parent) m_baudCombo->setCurrentText("115200"); form->addRow(tr("Baud rate:"), m_baudCombo); - // ── Data bits ───────────────────────────────────────────────────────────── m_dataBitsCombo = new QComboBox(this); m_dataBitsCombo->addItem("5", QSerialPort::Data5); m_dataBitsCombo->addItem("6", QSerialPort::Data6); @@ -46,7 +41,6 @@ ConnectDialog::ConnectDialog(QWidget *parent) m_dataBitsCombo->setCurrentText("8"); form->addRow(tr("Data bits:"), m_dataBitsCombo); - // ── Parity ──────────────────────────────────────────────────────────────── m_parityCombo = new QComboBox(this); m_parityCombo->addItem(tr("None"), QSerialPort::NoParity); m_parityCombo->addItem(tr("Even"), QSerialPort::EvenParity); @@ -55,21 +49,18 @@ ConnectDialog::ConnectDialog(QWidget *parent) m_parityCombo->addItem(tr("Mark"), QSerialPort::MarkParity); form->addRow(tr("Parity:"), m_parityCombo); - // ── Stop bits ───────────────────────────────────────────────────────────── m_stopBitsCombo = new QComboBox(this); m_stopBitsCombo->addItem("1", QSerialPort::OneStop); m_stopBitsCombo->addItem("1.5", QSerialPort::OneAndHalfStop); m_stopBitsCombo->addItem("2", QSerialPort::TwoStop); form->addRow(tr("Stop bits:"), m_stopBitsCombo); - // ── Flow control ────────────────────────────────────────────────────────── m_flowCombo = new QComboBox(this); m_flowCombo->addItem(tr("None"), QSerialPort::NoFlowControl); m_flowCombo->addItem(tr("RTS/CTS"), QSerialPort::HardwareControl); m_flowCombo->addItem(tr("XON/XOFF"), QSerialPort::SoftwareControl); form->addRow(tr("Flow control:"), m_flowCombo); - // ── Log file ────────────────────────────────────────────────────────────── auto *logRow = new QHBoxLayout(); m_logPathEdit = new QLineEdit(this); m_logPathEdit->setPlaceholderText(tr("(optional) path/to/output.log")); @@ -80,7 +71,6 @@ ConnectDialog::ConnectDialog(QWidget *parent) logRow->addWidget(browseBtn); form->addRow(tr("Log file:"), logRow); - // ── Buttons ─────────────────────────────────────────────────────────────── auto *buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); diff --git a/src/main.cpp b/src/main.cpp index 47ed4f9..e96c677 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "mainwindow.h" int main(int argc, char *argv[]) @@ -9,10 +10,11 @@ int main(int argc, char *argv[]) app.setApplicationVersion("1.0.0"); app.setOrganizationName("ChicaDev"); - // Prefer Fusion style for consistent cross-distro look app.setStyle(QStyleFactory::create("Fusion")); - // Optional: dark palette (comment out if you prefer system theme) + const QIcon appIcon(":/icons/UartscopeLogo.png"); + app.setWindowIcon(appIcon); + QPalette darkPalette; darkPalette.setColor(QPalette::Window, QColor(0x2b, 0x2b, 0x2b)); darkPalette.setColor(QPalette::WindowText, QColor(0xdc, 0xdc, 0xdc)); @@ -29,10 +31,6 @@ 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 7f4a67a..c9e9b00 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,9 +1,9 @@ #include "mainwindow.h" -#include #include #include #include #include +#include #include #include #include @@ -14,6 +14,7 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -26,7 +27,6 @@ MainWindow::MainWindow(QWidget *parent) setupStatusBar(); restoreSettings(); - // ── Worker thread ───────────────────────────────────────────────────────── m_thread = new QThread(this); m_worker = new SerialWorker(); m_worker->moveToThread(m_thread); @@ -46,9 +46,6 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { - // 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(); } @@ -66,11 +63,9 @@ void MainWindow::doShutdown() 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(); @@ -82,12 +77,10 @@ void MainWindow::doShutdown() void MainWindow::setupUi() { - // ── Outer horizontal splitter: (Raw+Table tabs) | right panel ──────────── m_mainSplitter = new QSplitter(Qt::Horizontal, this); + m_mainSplitter->setHandleWidth(5); auto *mainSplitter = m_mainSplitter; - mainSplitter->setHandleWidth(5); - // Left: tabs for Raw and Table views auto *tabs = new QTabWidget(mainSplitter); tabs->setTabPosition(QTabWidget::South); @@ -97,7 +90,6 @@ void MainWindow::setupUi() m_tableView = new TableView(tabs); tabs->addTab(m_tableView, tr("Table view")); - // Right: vertical splitter – Tag Monitor on top, Video below m_rightSplitter = new QSplitter(Qt::Vertical, mainSplitter); auto *rightSplitter = m_rightSplitter; rightSplitter->setHandleWidth(5); @@ -110,9 +102,8 @@ void MainWindow::setupUi() 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->setStretchFactor(1, 1); rightSplitter->setSizes({330, 330}); mainSplitter->addWidget(tabs); @@ -143,7 +134,6 @@ void MainWindow::setupToolBar() tb->addSeparator(); - // ── Auto-reconnect controls ─────────────────────────────────────────────── m_autoReconnectCb = new QCheckBox(tr("Auto-reconnect"), tb); m_autoReconnectCb->setChecked(true); m_autoReconnectCb->setToolTip(tr("Automatically reconnect when the port drops")); @@ -161,7 +151,6 @@ void MainWindow::setupToolBar() m_reconnectIntervalSb->setSingleStep(500); m_reconnectIntervalSb->setValue(2000); m_reconnectIntervalSb->setSuffix(tr(" ms")); - m_reconnectIntervalSb->setToolTip(tr("Interval between reconnect attempts")); connect(m_reconnectIntervalSb, QOverload::of(&QSpinBox::valueChanged), this, [this](int ms) { QMetaObject::invokeMethod(m_worker, [this, ms] { @@ -172,21 +161,18 @@ void MainWindow::setupToolBar() tb->addSeparator(); - // ── Tag filter ──────────────────────────────────────────────────────────── auto *filterAction = tb->addAction(tr("Tag filter…")); filterAction->setToolTip(tr("Choose which tags are hidden from the Raw view")); connect(filterAction, &QAction::triggered, this, &MainWindow::configureTagFilter); tb->addSeparator(); - // ── Format reference ────────────────────────────────────────────────────── 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); } @@ -197,7 +183,7 @@ void MainWindow::setupStatusBar() m_reconnectLabel = new QLabel(this); m_portLabel = new QLabel(this); - m_reconnectLabel->setStyleSheet("color: #e5a50a;"); // amber + m_reconnectLabel->setStyleSheet("color: #e5a50a;"); statusBar()->addWidget(m_stateLabel); statusBar()->addWidget(m_reconnectLabel); @@ -223,7 +209,6 @@ void MainWindow::onConnectClicked() Qt::QueuedConnection, Q_ARG(QString, m_lastConfig.logFilePath)); - // Push current reconnect settings to worker before opening const bool autoRec = m_autoReconnectCb->isChecked(); const int interval = m_reconnectIntervalSb->value(); QMetaObject::invokeMethod(m_worker, [this, autoRec, interval] { @@ -277,13 +262,13 @@ void MainWindow::onReconnecting(int attempt) void MainWindow::onClearScreen() { + // ANSI clear-screen received from the firmware: clear ALL three views + // (raw output, table, AND the tag monitor) plus the video preview frame. clearAllViews(); } void MainWindow::onError(const QString &message) { - // Only show a dialog for errors that arrive when we are NOT in reconnect - // mode – otherwise the amber status text is sufficient feedback. if (!m_autoReconnectCb->isChecked()) { QMessageBox::critical(this, tr("Serial error"), message); onPortClosed(); @@ -306,6 +291,8 @@ void MainWindow::clearAllViews() m_rawView->clear(); m_tableView->clear(); m_tagWidget->clearAll(); + if (m_videoWidget) + m_videoWidget->clearFrame(); } // ── Format reference dialog ──────────────────────────────────────────────── @@ -322,12 +309,11 @@ three parallel ways. You can use any combination. ──────────────────────────────────────────────────────────── 1. RAW OUTPUT (always active) ──────────────────────────────────────────────────────────── -Every line sent over UART is shown verbatim in the "Raw output" tab. -No special formatting needed. +Every line sent over UART is shown verbatim (with a timestamp) in the +"Raw output" tab. No special formatting needed. - uart_printf("Boot complete. Version 1.0\n"); - -ANSI clear-screen → clears ALL views simultaneously: +ANSI clear-screen → clears ALL views simultaneously (raw output, table, +tag monitor, and the video preview frame): uart_printf("\033[2J\033[H"); // standard VT100 clear-screen + cursor home @@ -336,17 +322,25 @@ ANSI clear-screen → clears ALL views simultaneously: ──────────────────────────────────────────────────────────── Any line that contains [TAGNAME] is intercepted and displayed in a dedicated panel in the Tag Monitor. The line is still shown in Raw output -(highlighted in cyan) so nothing is hidden. +(highlighted in cyan) unless the tag is added to the Tag Filter. TAG FORMAT: [TAGNAME] key1=value1 key2=value2 ... Rules: • Tag name: uppercase letters, digits, underscore. Examples: WDG, VIDEO, I2C_BUS - • Tag must start the meaningful content (leading whitespace is fine) • Key=value pairs are space-separated; values must not contain spaces • If no key=value pairs are present the raw string is shown as-is +LIST VALUES: separate multiple items in one value with ';' – they are +shown as a multi-line list under the same key in the Tag Monitor: + + uart_printf("[KICKSTART] Devs=trackdisk.device;input.device\n"); + + → Tag Monitor shows: + Devs trackdisk.device + input.device + Examples: uart_printf("[WDG] uptime=%lu free_heap=%lu temp=%d\n", uptime_ms, heap_free, cpu_temp); @@ -354,11 +348,7 @@ Examples: uart_printf("[VIDEO] line=%d hblank=%d vblank=%d dma_buf=%d\n", scanline, hblank_cycles, vblank_cycles, dma_remaining); - uart_printf("[AUDIO] buf=%d underruns=%lu vol=%d\n", - audio_buf_level, underrun_count, volume); - - uart_printf("[I2C] addr=0x%02X ack=%d err=%d\n", - i2c_addr, ack_received, error_code); + uart_printf("[KICKSTART] Libs=graphics.library;intuition.library;dos.library\n"); Each unique tag gets its own panel. Panels update in-place so repeated messages don't scroll past – perfect for periodic watchdog output. @@ -390,30 +380,25 @@ No special firmware changes needed. 5. COMPLETE EXAMPLE (Chica / Amiga hardware emulation) ──────────────────────────────────────────────────────────── void uart_status_update(void) { - // Watchdog heartbeat → Tag Monitor [WDG] uart_printf("[WDG] uptime=%lu free=%lu load=%d temp=%d\n", HAL_GetTick(), xPortGetFreeHeapSize(), cpu_load_percent, core_temp_c); - // Video timing → Tag Monitor [VIDEO] uart_printf("[VIDEO] line=%d hblank=%d vblank=%d copper=%d\n", current_scanline, hblank_ticks, vblank_ticks, copper_dma); - // Audio state → Tag Monitor [AUDIO] - uart_printf("[AUDIO] ch=%d buf=%d underruns=%lu\n", - active_channels, buffer_fill, total_underruns); + uart_printf("[KICKSTART] Devs=trackdisk.device;input.device;console.device\n"); + uart_printf("[KICKSTART] Libs=graphics.library;intuition.library\n"); // Structured CSV data → Table view uart_printf("%lu,%.1f,%d,%d\n", HAL_GetTick(), vcc_mv / 1000.0f, cpu_load_percent, core_temp_c); } -// Call from your main loop or a timer ISR, e.g. every 500 ms // Clear screen from firmware when you want a fresh start: // uart_printf("\033[2J\033[H"); )"; - // ── Dialog ──────────────────────────────────────────────────────────────── auto *dlg = new QDialog(this); dlg->setWindowTitle(tr("UARTScope – Format Reference")); dlg->resize(720, 620); @@ -475,7 +460,6 @@ void MainWindow::configureTagFilter() QFont mono("Monospace"); mono.setStyleHint(QFont::Monospace); edit->setFont(mono); - // Pre-populate with current filter QStringList current(m_suppressedTags.values()); current.sort(); edit->setPlainText(current.join('\n')); @@ -495,66 +479,12 @@ void MainWindow::configureTagFilter() if (!tag.isEmpty()) m_suppressedTags.insert(tag); } + // Persist immediately so the filter survives even an unexpected exit. + saveSettings(); } 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() @@ -567,7 +497,6 @@ void MainWindow::showAbout() 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;"); @@ -575,14 +504,12 @@ void MainWindow::showAbout() 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); @@ -598,7 +525,6 @@ void MainWindow::showAbout() headerLayout->addLayout(titleBlock); root->addWidget(header); - // ── Info grid ───────────────────────────────────────────────────────────── auto *body = new QWidget(dlg); body->setStyleSheet("background-color: #252526;"); auto *grid = new QGridLayout(body); @@ -628,7 +554,6 @@ void MainWindow::showAbout() 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); @@ -646,3 +571,54 @@ void MainWindow::showAbout() dlg->exec(); dlg->deleteLater(); } + +// ── Settings: save / restore ─────────────────────────────────────────────── + +void MainWindow::saveSettings() +{ + QSettings s("ChicaDev", "UARTScope"); + + s.setValue("window/geometry", saveGeometry()); + s.setValue("window/state", saveState()); + + if (m_mainSplitter) + s.setValue("splitter/main", m_mainSplitter->saveState()); + if (m_rightSplitter) + s.setValue("splitter/right", m_rightSplitter->saveState()); + + s.setValue("reconnect/enabled", m_autoReconnectCb->isChecked()); + s.setValue("reconnect/interval", m_reconnectIntervalSb->value()); + + QStringList tags(m_suppressedTags.values()); + tags.sort(); + s.setValue("tagfilter/suppressed", tags); + + s.sync(); // flush to disk immediately (important since we also call + // this mid-session from configureTagFilter()) +} + +void MainWindow::restoreSettings() +{ + QSettings s("ChicaDev", "UARTScope"); + + if (s.contains("window/geometry")) + restoreGeometry(s.value("window/geometry").toByteArray()); + if (s.contains("window/state")) + restoreState(s.value("window/state").toByteArray()); + + 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()); + + 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()); + + const QStringList tags = s.value("tagfilter/suppressed").toStringList(); + m_suppressedTags.clear(); + for (const QString &t : tags) + if (!t.isEmpty()) + m_suppressedTags.insert(t); +} diff --git a/src/rawview.cpp b/src/rawview.cpp index bdb9996..f3f7c44 100644 --- a/src/rawview.cpp +++ b/src/rawview.cpp @@ -24,7 +24,6 @@ void RawView::setupUi() mainLayout->setContentsMargins(4, 4, 4, 4); mainLayout->setSpacing(4); - // ── Toolbar ────────────────────────────────────────────────────────────── auto *toolbar = new QHBoxLayout(); m_searchEdit = new QLineEdit(this); @@ -33,7 +32,7 @@ void RawView::setupUi() connect(m_searchEdit, &QLineEdit::textChanged, this, &RawView::onSearch); m_autoScrollCb = new QCheckBox(tr("Auto-scroll"), this); - m_autoScrollCb->setChecked(true); + m_autoScrollCb->setChecked(true); // default ON connect(m_autoScrollCb, &QCheckBox::toggled, this, &RawView::onAutoScrollToggled); m_lineCountLbl = new QLabel(tr("Lines: 0"), this); @@ -54,11 +53,10 @@ void RawView::setupUi() toolbar->addWidget(m_clearBtn); mainLayout->addLayout(toolbar); - // ── Text area ───────────────────────────────────────────────────────────── m_textEdit = new QPlainTextEdit(this); m_textEdit->setReadOnly(true); m_textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); - m_textEdit->setMaximumBlockCount(0); // unlimited + m_textEdit->setMaximumBlockCount(0); QFont monoFont("Monospace"); monoFont.setStyleHint(QFont::Monospace); @@ -84,39 +82,27 @@ void RawView::applyColorScheme() void RawView::appendLine(const QString &line, const QSet &suppressedTags) { - // Check whether this line belongs to a suppressed tag if (!suppressedTags.isEmpty()) { const auto match = s_tagRe.match(line); - if (match.hasMatch()) { - if (suppressedTags.contains(match.captured(1).toUpper())) - return; // silently drop – it goes to the Tag Monitor only - } + if (match.hasMatch() && suppressedTags.contains(match.captured(1).toUpper())) + return; } ++m_lineCount; m_lineCountLbl->setText(tr("Lines: %1").arg(m_lineCount)); - // Prepend timestamp const QString ts = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); - const QString displayLine = QStringLiteral("[%1] %2").arg(ts, line); - - // Colour tag lines cyan, everything else default const bool hasTag = s_tagRe.match(line).hasMatch(); QTextCursor cursor(m_textEdit->document()); cursor.movePosition(QTextCursor::End); - QTextCharFormat fmt; - if (hasTag) - fmt.setForeground(QColor(0x56, 0xb6, 0xc2)); // cyan - else - fmt.setForeground(QColor(0xd4, 0xd4, 0xd4)); // default - - // Dim the timestamp portion QTextCharFormat tsFmt; tsFmt.setForeground(QColor(0x60, 0x60, 0x60)); - cursor.insertText(QStringLiteral("[%1] ").arg(ts), tsFmt); + + QTextCharFormat fmt; + fmt.setForeground(hasTag ? QColor(0x56, 0xb6, 0xc2) : QColor(0xd4, 0xd4, 0xd4)); cursor.insertText(line + '\n', fmt); if (m_autoScroll) diff --git a/src/serialworker.cpp b/src/serialworker.cpp index 7dc0a6e..a81b6e1 100644 --- a/src/serialworker.cpp +++ b/src/serialworker.cpp @@ -10,8 +10,8 @@ SerialWorker::SerialWorker(QObject *parent) m_reconnectTimer->setSingleShot(true); connect(m_reconnectTimer, &QTimer::timeout, this, &SerialWorker::tryReconnect); - connect(m_port, &QSerialPort::readyRead, this, &SerialWorker::onReadyRead); - connect(m_port, &QSerialPort::errorOccurred, this, &SerialWorker::onPortError); + connect(m_port, &QSerialPort::readyRead, this, &SerialWorker::onReadyRead); + connect(m_port, &QSerialPort::errorOccurred, this, &SerialWorker::onPortError); } SerialWorker::~SerialWorker() @@ -20,7 +20,7 @@ SerialWorker::~SerialWorker() closePort(); } -// ── Public slots ──────────────────────────────────────────────────────────── +// ── public slots ────────────────────────────────────────────────────────────── void SerialWorker::openPort(const QString &portName, qint32 baudRate, QSerialPort::DataBits dataBits, @@ -28,7 +28,6 @@ void SerialWorker::openPort(const QString &portName, qint32 baudRate, QSerialPort::StopBits stopBits, QSerialPort::FlowControl flowControl) { - // Save config for auto-reconnect m_portName = portName; m_baudRate = baudRate; m_dataBits = dataBits; @@ -55,6 +54,7 @@ void SerialWorker::openPort(const QString &portName, qint32 baudRate, return; } m_buffer.clear(); + m_scanTail.clear(); m_reconnectAttempt = 0; emit portOpened(); } @@ -107,7 +107,7 @@ void SerialWorker::stopLogging() } } -// ── Private slots ─────────────────────────────────────────────────────────── +// ── private slots ─────────────────────────────────────────────────────────── void SerialWorker::onReadyRead() { @@ -119,7 +119,6 @@ void SerialWorker::onPortError(QSerialPort::SerialPortError err) if (err == QSerialPort::NoError) return; - // ResourceError = device physically disconnected (USB pull, power loss) const bool fatal = (err == QSerialPort::ResourceError || err == QSerialPort::DeviceNotFoundError); @@ -132,8 +131,6 @@ void SerialWorker::onPortError(QSerialPort::SerialPortError err) emit errorOccurred(m_port->errorString()); } } - // Non-fatal errors (framing, parity, etc.) are silently ignored to avoid - // spamming the user during noisy serial sessions. } void SerialWorker::tryReconnect() @@ -153,6 +150,7 @@ void SerialWorker::tryReconnect() if (m_port->open(QIODevice::ReadOnly)) { m_buffer.clear(); + m_scanTail.clear(); m_reconnectAttempt = 0; emit portOpened(); } else { @@ -160,7 +158,7 @@ void SerialWorker::tryReconnect() } } -// ── Private helpers ───────────────────────────────────────────────────────── +// ── private helpers ───────────────────────────────────────────────────────── void SerialWorker::scheduleReconnect() { @@ -170,23 +168,48 @@ void SerialWorker::scheduleReconnect() void SerialWorker::processRawData(const QByteArray &data) { - // Scan raw bytes for ANSI clear-screen BEFORE UTF-8 conversion so we - // never miss a sequence that straddles a read boundary. - // We look for ESC[2J (optionally followed by ESC[H or ESC[1;1H) - // ESC = 0x1B - const QByteArray esc2j("\x1b[2J"); - if (data.contains(esc2j)) { - emit clearScreen(); - // Strip all ANSI escape sequences from the data before further processing - // so they don't clutter the raw view. + // ── ANSI clear-screen detection ──────────────────────────────────────── + // We scan a buffer that includes a small tail from the PREVIOUS chunk so + // that a sequence split across two readyRead() calls (e.g. "...\x1b" + + // "[2J\x1b[H...") is still detected reliably. The clear sequence itself + // is also stripped from what reaches processLine()/the line buffer. + QByteArray combined = m_scanTail + data; + bool sawClear = false; + + // Look for ESC[2J (the actual screen-clear command). ESC[H / ESC[1;1H + // (cursor-home) commonly follows but isn't required for us to clear. + const QByteArray clearSeq("\x1b[2J"); + int clearPos = combined.indexOf(clearSeq); + while (clearPos != -1) { + sawClear = true; + // Remove the matched sequence so it doesn't leak into the text stream + combined.remove(clearPos, clearSeq.size()); + clearPos = combined.indexOf(clearSeq, clearPos); } - // Strip all ANSI escape sequences for display - // Sequence: ESC [ + // Keep the last few bytes as the new tail (in case a sequence is cut off + // at the very end of this chunk); only forward the rest. + constexpr int kTailLen = 8; // longer than any escape sequence we match + QByteArray toProcess; + if (combined.size() > kTailLen) { + toProcess = combined.left(combined.size() - kTailLen); + m_scanTail = combined.right(kTailLen); + } else { + // Not enough data yet to safely flush – hold everything as tail. + m_scanTail = combined; + } + + if (sawClear) + emit clearScreen(); + + if (toProcess.isEmpty()) + return; + + // Strip remaining (non-clear) ANSI escape sequences for clean display. static const QRegularExpression ansiRe( "\x1b(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])"); - QString text = QString::fromUtf8(data); + QString text = QString::fromUtf8(toProcess); text.remove(ansiRe); m_buffer += text; diff --git a/src/tableview.cpp b/src/tableview.cpp index 7479357..4846dc3 100644 --- a/src/tableview.cpp +++ b/src/tableview.cpp @@ -10,7 +10,6 @@ TableView::TableView(QWidget *parent) mainLayout->setContentsMargins(4, 4, 4, 4); mainLayout->setSpacing(4); - // ── Toolbar ────────────────────────────────────────────────────────────── auto *toolbar = new QHBoxLayout(); m_delimCombo = new QComboBox(this); @@ -44,7 +43,6 @@ TableView::TableView(QWidget *parent) toolbar->addWidget(m_clearBtn); mainLayout->addLayout(toolbar); - // ── Table ───────────────────────────────────────────────────────────────── m_table = new QTableWidget(this); m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_table->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -66,7 +64,6 @@ void TableView::onDelimiterChanged(int index) void TableView::onMaxRowsChanged() { m_maxRows = m_maxRowsEdit->text().toInt(); - // Trim excess rows while (m_rows.size() > m_maxRows) m_rows.removeFirst(); rebuildTable(); @@ -87,14 +84,13 @@ void TableView::appendLine(const QString &line) if (line.isEmpty()) return; - // Header row starts with '#' if (!m_headersSet && line.startsWith('#')) { - parseHeaders(line.mid(1)); // strip leading '#' + parseHeaders(line.mid(1)); return; } if (!tryAppendRow(line)) - return; // didn't match expected column count + return; if (m_autoScroll) m_table->scrollToBottom(); @@ -114,7 +110,6 @@ bool TableView::tryAppendRow(const QString &line) { const QStringList cells = line.split(m_delimiter); - // Auto-detect column count on the first data row if no header was given if (!m_headersSet) { m_headers.clear(); for (int i = 0; i < cells.size(); ++i) @@ -125,9 +120,8 @@ bool TableView::tryAppendRow(const QString &line) } if (cells.size() != m_headers.size()) - return false; // column mismatch – skip row + return false; - // Enforce max rows m_rows.append(cells); if (m_rows.size() > m_maxRows) m_rows.removeFirst(); @@ -140,7 +134,6 @@ bool TableView::tryAppendRow(const QString &line) m_table->setItem(row, col, item); } - // Remove oldest visual row if over limit if (m_table->rowCount() > m_maxRows) m_table->removeRow(0); diff --git a/src/tagpanel.cpp b/src/tagpanel.cpp index 72bc747..8653814 100644 --- a/src/tagpanel.cpp +++ b/src/tagpanel.cpp @@ -12,25 +12,22 @@ TagPanel::TagPanel(const QString &tag, QWidget *parent) layout->setContentsMargins(6, 14, 6, 6); layout->setSpacing(4); - // ── Table for key=value pairs ───────────────────────────────────────────── m_table = new QTableWidget(0, 2, this); m_table->setHorizontalHeaderLabels({tr("Key"), tr("Value")}); m_table->horizontalHeader()->setStretchLastSection(true); m_table->verticalHeader()->hide(); m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_table->setAlternatingRowColors(true); - m_table->setMaximumHeight(200); + m_table->setMaximumHeight(220); m_table->verticalHeader()->setDefaultSectionSize(22); layout->addWidget(m_table); - // ── Fallback label for non-kv data ──────────────────────────────────────── m_rawLabel = new QLabel(this); m_rawLabel->setWordWrap(true); m_rawLabel->setStyleSheet("font-family: monospace;"); m_rawLabel->hide(); layout->addWidget(m_rawLabel); - // ── Copy button ─────────────────────────────────────────────────────────── auto *btnRow = new QHBoxLayout(); btnRow->addStretch(); auto *copyBtn = new QPushButton(tr("📋 Copy"), this); @@ -61,10 +58,59 @@ void TagPanel::parseKeyValue(const QString &value) auto it = kvRe.globalMatch(value); while (it.hasNext()) { const auto match = it.next(); - ensureRow(match.captured(1)); - const int row = m_keys.indexOf(match.captured(1)); - m_table->item(row, 1)->setText(match.captured(2)); - m_table->item(row, 1)->setBackground(QColor(0x2d, 0x5a, 0x2d)); // flash green + const QString key = match.captured(1); + const QString raw = match.captured(2); + + // Split on ';' -> list of values. A plain (non-list) value yields + // a QStringList with exactly one element, which still goes through + // the same code path for consistency. + QStringList values = raw.split(';', Qt::SkipEmptyParts); + if (values.isEmpty()) + values << raw; // preserve even an empty/odd value as-is + + setKeyValues(key, values); + } +} + +void TagPanel::setKeyValues(const QString &key, const QStringList &values) +{ + const int newCount = values.size(); + + if (m_keyRanges.contains(key)) { + // Key already exists – remove its current rows, then re-insert at + // the same starting position with the new row count. + const KeyRange old = m_keyRanges[key]; + for (int i = 0; i < old.count; ++i) + m_table->removeRow(old.firstRow); + + // Shift all OTHER key ranges that started after this one + for (auto it = m_keyRanges.begin(); it != m_keyRanges.end(); ++it) { + if (it.key() != key && it->firstRow > old.firstRow) + it->firstRow += (newCount - old.count); + } + + int insertAt = old.firstRow; + for (int i = 0; i < newCount; ++i) { + m_table->insertRow(insertAt + i); + auto *keyItem = new QTableWidgetItem(i == 0 ? key : QString()); + auto *valItem = new QTableWidgetItem(values[i]); + valItem->setBackground(QColor(0x2d, 0x5a, 0x2d)); // flash green on update + m_table->setItem(insertAt + i, 0, keyItem); + m_table->setItem(insertAt + i, 1, valItem); + } + m_keyRanges[key] = {insertAt, newCount}; + } else { + // New key – append at the bottom + const int insertAt = m_table->rowCount(); + for (int i = 0; i < newCount; ++i) { + m_table->insertRow(insertAt + i); + auto *keyItem = new QTableWidgetItem(i == 0 ? key : QString()); + auto *valItem = new QTableWidgetItem(values[i]); + valItem->setBackground(QColor(0x2d, 0x5a, 0x2d)); + m_table->setItem(insertAt + i, 0, keyItem); + m_table->setItem(insertAt + i, 1, valItem); + } + m_keyRanges[key] = {insertAt, newCount}; } } @@ -73,27 +119,22 @@ void TagPanel::showRaw(const QString &value) m_rawLabel->setText(value); } -void TagPanel::ensureRow(const QString &key) -{ - if (m_keys.contains(key)) - return; - m_keys.append(key); - const int row = m_table->rowCount(); - m_table->insertRow(row); - m_table->setItem(row, 0, new QTableWidgetItem(key)); - m_table->setItem(row, 1, new QTableWidgetItem(QString())); -} - void TagPanel::copyToClipboard() { QStringList lines; lines << QStringLiteral("[%1]").arg(m_tag); if (m_table->isVisible()) { + QString lastKey; for (int r = 0; r < m_table->rowCount(); ++r) { const QString key = m_table->item(r, 0)->text(); const QString val = m_table->item(r, 1)->text(); - lines << QStringLiteral(" %1 = %2").arg(key, val); + if (!key.isEmpty()) { + lines << QStringLiteral(" %1 = %2").arg(key, val); + lastKey = key; + } else { + lines << QStringLiteral(" %1").arg(val); // continuation row + } } } else { lines << QStringLiteral(" %1").arg(m_rawLabel->text()); diff --git a/src/tagwidget.cpp b/src/tagwidget.cpp index ac1b2ba..49f6b5a 100644 --- a/src/tagwidget.cpp +++ b/src/tagwidget.cpp @@ -7,7 +7,6 @@ TagWidget::TagWidget(QWidget *parent) outerLayout->setContentsMargins(4, 4, 4, 4); outerLayout->setSpacing(4); - // Header row auto *headerLayout = new QHBoxLayout(); auto *titleLabel = new QLabel(tr("Tag Monitor"), this); auto *clearBtn = new QPushButton(tr("Clear all"), this); @@ -18,7 +17,6 @@ TagWidget::TagWidget(QWidget *parent) headerLayout->addWidget(clearBtn); outerLayout->addLayout(headerLayout); - // Scrollable panel area auto *scrollArea = new QScrollArea(this); scrollArea->setWidgetResizable(true); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -26,7 +24,7 @@ TagWidget::TagWidget(QWidget *parent) m_panelLayout = new QVBoxLayout(container); m_panelLayout->setContentsMargins(0, 0, 0, 0); m_panelLayout->setSpacing(6); - m_panelLayout->addStretch(); // pushes panels to the top + m_panelLayout->addStretch(); scrollArea->setWidget(container); outerLayout->addWidget(scrollArea, 1); } @@ -36,7 +34,6 @@ void TagWidget::handleTag(const QString &tag, const QString &value) if (!m_panels.contains(tag)) { auto *panel = new TagPanel(tag, this); m_panels.insert(tag, panel); - // Insert before the stretch at the end m_panelLayout->insertWidget(m_panelLayout->count() - 1, panel); } m_panels[tag]->update(value); diff --git a/src/v4l2worker.cpp b/src/v4l2worker.cpp index 9c38061..a3349a4 100644 --- a/src/v4l2worker.cpp +++ b/src/v4l2worker.cpp @@ -10,7 +10,6 @@ #include #include -#include static int xioctl(int fd, unsigned long req, void *arg) { @@ -19,16 +18,9 @@ static int xioctl(int fd, unsigned long req, void *arg) return r; } -V4l2Worker::V4l2Worker(QObject *parent) - : QObject(parent) -{} +V4l2Worker::V4l2Worker(QObject *parent) : QObject(parent) {} -V4l2Worker::~V4l2Worker() -{ - stop(); -} - -// ── public slots ────────────────────────────────────────────────────────────── +V4l2Worker::~V4l2Worker() { stop(); } void V4l2Worker::startCapture(const QString &device, int width, int height) { @@ -39,29 +31,19 @@ void V4l2Worker::startCapture(const QString &device, int width, int 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::stopCapture() { stop(); } void V4l2Worker::stop() { m_running.store(false); if (m_captureThread.joinable()) - m_captureThread.join(); // wait for captureLoop() to exit + m_captureThread.join(); closeDevice(); } -// ── capture loop (runs in m_captureThread) ──────────────────────────────────── - void V4l2Worker::captureLoop() { v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -75,7 +57,7 @@ void V4l2Worker::captureLoop() fd_set fds; FD_ZERO(&fds); FD_SET(m_fd, &fds); - timeval tv{0, 100000}; // 100 ms timeout → checks m_running 10x/sec + timeval tv{0, 100000}; const int r = ::select(m_fd + 1, &fds, nullptr, nullptr, &tv); if (r == 0) continue; if (r == -1) { @@ -108,8 +90,6 @@ void V4l2Worker::captureLoop() emit captureStopped(); } -// ── device open / close ─────────────────────────────────────────────────────── - bool V4l2Worker::openDevice(const QString &device, int width, int height) { m_width = width; @@ -134,10 +114,7 @@ bool V4l2Worker::openDevice(const QString &device, int width, int height) } const uint32_t preferred[] = { - V4L2_PIX_FMT_MJPEG, - V4L2_PIX_FMT_YUYV, - V4L2_PIX_FMT_NV12, - 0 + V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_NV12, 0 }; m_pixFmt = 0; diff --git a/src/videowidget.cpp b/src/videowidget.cpp index 79a3a61..e308b01 100644 --- a/src/videowidget.cpp +++ b/src/videowidget.cpp @@ -8,7 +8,6 @@ #include #include -// Enumerate /dev/video* entries static QStringList enumerateVideoDevices() { QStringList devices; @@ -19,8 +18,6 @@ static QStringList enumerateVideoDevices() return devices; } -// ── ctor / dtor ─────────────────────────────────────────────────────────────── - VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { @@ -32,20 +29,20 @@ VideoWidget::VideoWidget(QWidget *parent) connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater); - connect(m_worker, &V4l2Worker::newFrame, this, &VideoWidget::onNewFrame, + 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) { + 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] { + 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) { + 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")); @@ -60,8 +57,6 @@ void VideoWidget::shutdown() 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()) { @@ -77,8 +72,6 @@ VideoWidget::~VideoWidget() shutdown(); } -// ── UI setup ───────────────────────────────────────────────────────────────── - void VideoWidget::setupUi() { setMinimumHeight(140); @@ -87,7 +80,6 @@ void VideoWidget::setupUi() layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(3); - // ── Controls row ────────────────────────────────────────────────────────── auto *ctrlRow = new QHBoxLayout(); ctrlRow->setSpacing(4); @@ -97,7 +89,6 @@ void VideoWidget::setupUi() 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); @@ -111,7 +102,6 @@ void VideoWidget::setupUi() 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); @@ -121,24 +111,17 @@ void VideoWidget::setupUi() 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(); + if (m_running) stopCapture(); + else startCapture(); } void VideoWidget::onFreeze(bool frozen) @@ -172,7 +155,14 @@ void VideoWidget::onNewFrame(const QImage &frame) return; m_frame = frame; updateScaled(); - update(); // trigger paintEvent + update(); +} + +void VideoWidget::clearFrame() +{ + m_frame = QImage(); + m_scaled = QImage(); + update(); } void VideoWidget::refreshDeviceList() @@ -182,14 +172,11 @@ void VideoWidget::refreshDeviceList() 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(); @@ -197,7 +184,6 @@ void VideoWidget::startCapture() 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), @@ -210,11 +196,8 @@ 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; @@ -226,8 +209,6 @@ void VideoWidget::updateScaled() void VideoWidget::paintEvent(QPaintEvent *) { QPainter p(this); - - // Background p.fillRect(rect(), QColor(0x10, 0x10, 0x10)); if (m_scaled.isNull()) { @@ -237,14 +218,12 @@ void VideoWidget::paintEvent(QPaintEvent *) 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)); diff --git a/uartscope-git/PKGBUILD b/uartscope-git/PKGBUILD index 0595ee7..54b6e76 100644 --- a/uartscope-git/PKGBUILD +++ b/uartscope-git/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: diabolus pkgname=uartscope -pkgver=1.0.0.r1.g2181f25 +pkgver=1.0.0.r2.g92168ee 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 a5eff86..d613a38 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.r1.g2181f25-1 +pkgver = 1.0.0.r2.g92168ee-1 pkgarch = x86_64 -pkgbuild_sha256sum = 367d5f02c03027788d532bdeda7084f79008b9dcf902393b209ad7f2ae43859c +pkgbuild_sha256sum = ec7fe9ddf4a5966cba66edc3f38e169bdaeeffd2a0b4206e481794ce35a30737 packager = Unknown Packager -builddate = 1780988717 +builddate = 1781039770 builddir = /home/diabolus/Arbeit/Projekt-Hirnfrei/uartscope/uartscope-git startdir = /home/diabolus/Arbeit/Projekt-Hirnfrei/uartscope/uartscope-git buildtool = makepkg @@ -2790,7 +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 = uartscope-1.0.0.r1.g2181f25-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 diff --git a/uartscope-git/pkg/uartscope/.MTREE b/uartscope-git/pkg/uartscope/.MTREE index 1d33d2f..8c4e3e1 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 e91017d..e25dff6 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.r1.g2181f25-1 +pkgver = 1.0.0.r2.g92168ee-1 pkgdesc = Qt6-based UART serial monitor with tag monitoring, table view and auto-reconnect url = https://git.projekt-hirnfrei.de/diabolus/uartscope -builddate = 1780988717 +builddate = 1781039770 packager = Unknown Packager -size = 209170 +size = 381553 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 b21e59b..ec364e9 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/pkg/uartscope/usr/share/applications/uartscope.desktop b/uartscope-git/pkg/uartscope/usr/share/applications/uartscope.desktop index 5c42a21..d66b061 100644 --- a/uartscope-git/pkg/uartscope/usr/share/applications/uartscope.desktop +++ b/uartscope-git/pkg/uartscope/usr/share/applications/uartscope.desktop @@ -2,7 +2,7 @@ Name=UARTScope Comment=UART Serial Monitor Exec=uartscope -Icon=utilities-terminal +Icon=uartscope Terminal=false Type=Application Categories=Development;Utility; diff --git a/uartscope-git/src/uartscope b/uartscope-git/src/uartscope index 2181f25..92168ee 160000 --- a/uartscope-git/src/uartscope +++ b/uartscope-git/src/uartscope @@ -1 +1 @@ -Subproject commit 2181f254d29960cffe3b1f6158a97b3c65fb0d15 +Subproject commit 92168ee5dc7cfe0e6711207e314b6bd3acb8f291 diff --git a/uartscope-git/uartscope/FETCH_HEAD b/uartscope-git/uartscope/FETCH_HEAD index fcc6eb0..6c494a9 100644 --- a/uartscope-git/uartscope/FETCH_HEAD +++ b/uartscope-git/uartscope/FETCH_HEAD @@ -1,2 +1,2 @@ -2181f254d29960cffe3b1f6158a97b3c65fb0d15 not-for-merge branch 'main' of https://git.projekt-hirnfrei.de/diabolus/uartscope +92168ee5dc7cfe0e6711207e314b6bd3acb8f291 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