diff --git a/client/about.ui b/client/about.ui index 3953af0..6fb2711 100644 --- a/client/about.ui +++ b/client/about.ui @@ -104,7 +104,7 @@ - <a href="http://ostinato.org">http://ostinato.org</a><br><a href="http://twitter.com/ostinato">@ostinato</a> + <a href="https://ostinato.org">ostinato.org</a><br><a href="https://twitter.com/ostinato">@ostinato</a> Qt::RichText @@ -155,7 +155,7 @@ Icons (c): Mark James (http://www.famfamfam.com/lab/icons/silk/) - <p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p><p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a></p> + <p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p><p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a></p> Qt::RichText diff --git a/client/devicegroupmodel.cpp b/client/devicegroupmodel.cpp index 5b90765..0b20eff 100644 --- a/client/devicegroupmodel.cpp +++ b/client/devicegroupmodel.cpp @@ -118,7 +118,7 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const return v; return QString("None"); case Qt::TextAlignmentRole: - return Qt::AlignRight; + return static_cast(Qt::AlignRight|Qt::AlignVCenter); default: break; } @@ -129,7 +129,7 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: return qMax(vlanCount(devGrp), 1)*devGrp->device_count(); case Qt::TextAlignmentRole: - return Qt::AlignRight; + return static_cast(Qt::AlignRight|Qt::AlignVCenter); default: break; } diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp index 64bbbfe..1a4b303 100644 --- a/client/deviceswidget.cpp +++ b/client/deviceswidget.cpp @@ -32,7 +32,7 @@ DevicesWidget::DevicesWidget(QWidget *parent) setupUi(this); deviceGroupList->setVisible(deviceConfig->isChecked()); deviceList->setVisible(deviceInfo->isChecked()); - refresh->setVisible(deviceInfo->isChecked()); + setDeviceInfoButtonsVisible(deviceInfo->isChecked()); deviceDetail->hide(); deviceGroupList->verticalHeader()->setDefaultSectionSize( @@ -162,7 +162,7 @@ void DevicesWidget::updateDeviceViewActions() void DevicesWidget::on_deviceInfo_toggled(bool checked) { - refresh->setVisible(checked); + setDeviceInfoButtonsVisible(checked); deviceGroupList->setHidden(checked); deviceList->setVisible(checked); deviceDetail->hide(); @@ -236,6 +236,40 @@ void DevicesWidget::on_refresh_clicked() .getDeviceInfo(portGroups_->port(currentPortIndex_).id()); } +void DevicesWidget::on_resolveNeighbors_clicked() +{ + if (!portGroups_) + return; + + Q_ASSERT(portGroups_->isPort(currentPortIndex_)); + QModelIndex curPortGroup = portGroups_->getPortModel() + ->parent(currentPortIndex_); + Q_ASSERT(curPortGroup.isValid()); + Q_ASSERT(portGroups_->isPortGroup(curPortGroup)); + + deviceDetail->hide(); + QList portList({portGroups_->port(currentPortIndex_).id()}); + portGroups_->portGroup(curPortGroup).resolveDeviceNeighbors(&portList); + portGroups_->portGroup(curPortGroup).getDeviceInfo(portList.at(0)); +} + +void DevicesWidget::on_clearNeighbors_clicked() +{ + if (!portGroups_) + return; + + Q_ASSERT(portGroups_->isPort(currentPortIndex_)); + QModelIndex curPortGroup = portGroups_->getPortModel() + ->parent(currentPortIndex_); + Q_ASSERT(curPortGroup.isValid()); + Q_ASSERT(portGroups_->isPortGroup(curPortGroup)); + + deviceDetail->hide(); + QList portList({portGroups_->port(currentPortIndex_).id()}); + portGroups_->portGroup(curPortGroup).clearDeviceNeighbors(&portList); + portGroups_->portGroup(curPortGroup).getDeviceInfo(portList.at(0)); +} + void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index) { if (!index.isValid() || !portGroups_) @@ -247,3 +281,11 @@ void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index) deviceDetail->setModel(detailModel); deviceDetail->setVisible(detailModel != NULL); } + +void DevicesWidget::setDeviceInfoButtonsVisible(bool show) +{ + refresh->setVisible(show); + arpNdpLabel->setVisible(show); + resolveNeighbors->setVisible(show); + clearNeighbors->setVisible(show); +} diff --git a/client/deviceswidget.h b/client/deviceswidget.h index d5ef524..d1eb38c 100644 --- a/client/deviceswidget.h +++ b/client/deviceswidget.h @@ -49,10 +49,14 @@ private slots: void on_deviceGroupList_activated(const QModelIndex &index); void on_refresh_clicked(); + void on_resolveNeighbors_clicked(); + void on_clearNeighbors_clicked(); void when_deviceList_currentChanged(const QModelIndex &index); private: + void setDeviceInfoButtonsVisible(bool show); + PortGroupList *portGroups_; QModelIndex currentPortIndex_; }; diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui index 2e09917..ec76836 100644 --- a/client/deviceswidget.ui +++ b/client/deviceswidget.ui @@ -36,19 +36,6 @@ - - - - Qt::Horizontal - - - - 131 - 23 - - - - @@ -66,6 +53,60 @@ + + + + Qt::Horizontal + + + + 131 + 23 + + + + + + + + ARP/ND + + + + + + + Resolve Neighbors + + + Resolve Device Neighbors on selected port(s) + + + + + + + :/icons/neighbor_resolve.png:/icons/neighbor_resolve.png + + + + + + + Clear Neighbors + + + Clear Device Neighbors on selected port(s) + + + + + + + :/icons/neighbor_clear.png:/icons/neighbor_clear.png + + + diff --git a/client/icononlydelegate.h b/client/icononlydelegate.h new file mode 100644 index 0000000..ef7369d --- /dev/null +++ b/client/icononlydelegate.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _ICON_ONLY_DELEGATE +#define _ICON_ONLY_DELEGATE + +#include + +class IconOnlyDelegate : public QStyledItemDelegate +{ + using QStyledItemDelegate::QStyledItemDelegate; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const + { + QStyleOptionViewItem opt = option; + opt.decorationPosition = QStyleOptionViewItem::Top; + opt.features &= ~QStyleOptionViewItem::HasDisplay; + QStyledItemDelegate::paint(painter, opt, index); + } +}; + +#endif + diff --git a/client/icons/deco_exclusive.png b/client/icons/deco_exclusive.png deleted file mode 100644 index 911da3f..0000000 Binary files a/client/icons/deco_exclusive.png and /dev/null differ diff --git a/client/icons/donate.png b/client/icons/donate.png new file mode 100644 index 0000000..0ca9074 Binary files /dev/null and b/client/icons/donate.png differ diff --git a/client/icons/frag_capture.png b/client/icons/frag_capture.png new file mode 100644 index 0000000..2702f43 Binary files /dev/null and b/client/icons/frag_capture.png differ diff --git a/client/icons/frag_exclusive.png b/client/icons/frag_exclusive.png new file mode 100644 index 0000000..89e1aea Binary files /dev/null and b/client/icons/frag_exclusive.png differ diff --git a/client/icons/frag_link_down.png b/client/icons/frag_link_down.png new file mode 100644 index 0000000..897ac8a Binary files /dev/null and b/client/icons/frag_link_down.png differ diff --git a/client/icons/frag_link_unknown.png b/client/icons/frag_link_unknown.png new file mode 100644 index 0000000..3272798 Binary files /dev/null and b/client/icons/frag_link_unknown.png differ diff --git a/client/icons/frag_link_up.png b/client/icons/frag_link_up.png new file mode 100644 index 0000000..552cd58 Binary files /dev/null and b/client/icons/frag_link_up.png differ diff --git a/client/icons/frag_transmit.png b/client/icons/frag_transmit.png new file mode 100644 index 0000000..03132ed Binary files /dev/null and b/client/icons/frag_transmit.png differ diff --git a/client/icons/sound_mute.png b/client/icons/sound_mute.png index b652d2a..e3399b0 100644 Binary files a/client/icons/sound_mute.png and b/client/icons/sound_mute.png differ diff --git a/client/icons/sound_none.png b/client/icons/sound_none.png index b497ebd..6f81a86 100644 Binary files a/client/icons/sound_none.png and b/client/icons/sound_none.png differ diff --git a/client/icons/transmit_on.png b/client/icons/transmit_on.png new file mode 100644 index 0000000..f54bf73 Binary files /dev/null and b/client/icons/transmit_on.png differ diff --git a/client/jumpurl.h b/client/jumpurl.h index d91a3be..ddc9add 100644 --- a/client/jumpurl.h +++ b/client/jumpurl.h @@ -28,7 +28,7 @@ inline QString jumpUrl( QString medium="hint", QString name="help") { - return QString("http://jump.ostinato.org/" + keyword + "?" + return QString("https://jump.ostinato.org/" + keyword + "?" + "utm_source=" + source + "&" + "utm_medium=" + medium + "&" + "utm_campaign=" + name); diff --git a/client/log.h b/client/log.h new file mode 100644 index 0000000..b911dfc --- /dev/null +++ b/client/log.h @@ -0,0 +1,87 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _LOG_H +#define _LOG_H + +#include "logsmodel.h" + +extern LogsModel *appLogs; + +inline static void logError(QString port, QString message) +{ + appLogs->log(LogsModel::kError, port, message); +} + +inline static void logError(quint32 portGroupId, quint32 portId,QString message) +{ + logError(QString("%1-%2").arg(portGroupId).arg(portId), message); +} + +inline static void logError(quint32 portGroupId, QString message) +{ + logError(QString("%1-*").arg(portGroupId), message); +} + +inline static void logError(QString message) +{ + logError(QString("--"), message); +} + +inline static void logWarn(QString port, QString message) +{ + appLogs->log(LogsModel::kWarning, port, message); +} + +inline static void logWarn(quint32 portGroupId, quint32 portId,QString message) +{ + logWarn(QString("%1-%2").arg(portGroupId).arg(portId), message); +} + +inline static void logWarn(quint32 portGroupId, QString message) +{ + logWarn(QString("%1-*").arg(portGroupId), message); +} + +inline static void logWarn(QString message) +{ + logWarn(QString("--"), message); +} + +inline static void logInfo(QString port, QString message) +{ + appLogs->log(LogsModel::kInfo, port, message); +} + +inline static void logInfo(quint32 portGroupId, quint32 portId,QString message) +{ + logInfo(QString("%1-%2").arg(portGroupId).arg(portId), message); +} + +inline static void logInfo(quint32 portGroupId, QString message) +{ + logInfo(QString("%1-*").arg(portGroupId), message); +} + +inline static void logInfo(QString message) +{ + logInfo(QString("--"), message); +} +#endif + diff --git a/client/logsmodel.cpp b/client/logsmodel.cpp new file mode 100644 index 0000000..e749db9 --- /dev/null +++ b/client/logsmodel.cpp @@ -0,0 +1,145 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#include "logsmodel.h" + +#include + +// XXX: Keep the enum in sync with it's string +enum { + kTimeStamp, + kLogLevel, + kPort, + kMessage, + kFieldCount +}; +static QStringList columnTitles = QStringList() + << "Timestamp" + << "Level" + << "Port" + << "Message"; + +static QStringList levelTitles = QStringList() + << "Info" + << "Warning" + << "Error"; + +LogsModel::LogsModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +int LogsModel::rowCount(const QModelIndex &/*parent*/) const +{ + return logs_.size(); +} + +int LogsModel::columnCount(const QModelIndex &/*parent*/) const +{ + return kFieldCount; +} + +QVariant LogsModel::headerData( + int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (orientation) { + case Qt::Horizontal: // Column Header + return columnTitles.at(section); + case Qt::Vertical: // Row Header + return QVariant(); + default: + break; + } + return QVariant(); +} + +QVariant LogsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || (index.row() >= logs_.size())) + return QVariant(); + + if (role == Qt::ForegroundRole) { + switch(logs_.at(index.row()).logLevel) { + case kError: + return QBrush(QColor("darkred")); + case kWarning: + return QBrush(QColor("orangered")); + case kInfo: + default: + return QVariant(); + } + } + + if (role != Qt::DisplayRole) + return QVariant(); + + switch (index.column()) { + case kTimeStamp: + return logs_.at(index.row()).timeStamp.toString("hh:mm:ss.zzz"); + case kLogLevel: + return levelTitles.at(logs_.at(index.row()).logLevel); + case kPort: + return logs_.at(index.row()).port; + case kMessage: + return logs_.at(index.row()).message; + default: + break; + } + + return QVariant(); +} + +// --------------------------------------------- // +// Slots +// --------------------------------------------- // +void LogsModel::clear() +{ + beginResetModel(); + logs_.clear(); + endResetModel(); +} + +void LogsModel::setLogLevel(int level) +{ + currentLevel_ = static_cast(level % kLevelCount); + log(currentLevel_, QString("--"), + QString("Log level changed to %1 or higher") + .arg(levelTitles.at(currentLevel_))); +} + +void LogsModel::log(int logLevel,QString port, QString message) +{ + if (logLevel < currentLevel_) + return; + + // TODO: discard logs older than some threshold + + //qDebug("adding log %u %s", logs_.size(), qPrintable(message)); + beginInsertRows(QModelIndex(), logs_.size(), logs_.size()); + Log l; + logs_.append(l); + logs_.last().timeStamp = QTime::currentTime(); + logs_.last().logLevel = logLevel; + logs_.last().port = port; + logs_.last().message = message; + endInsertRows(); +} diff --git a/client/logsmodel.h b/client/logsmodel.h new file mode 100644 index 0000000..a7edb17 --- /dev/null +++ b/client/logsmodel.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _LOGS_MODEL_H +#define _LOGS_MODEL_H + +#include +#include + +class LogsModel: public QAbstractTableModel +{ + Q_OBJECT +public: + enum LogLevel { // FIXME: use enum class? + kInfo, + kWarning, + kError, + kLevelCount + }; + +public: + LogsModel(QObject *parent = 0); + + int rowCount(const QModelIndex &parent=QModelIndex()) const; + int columnCount(const QModelIndex &parent=QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + +public slots: + void clear(); + void setLogLevel(int level); + void log(int logLevel,QString port, QString message); + +private: + struct Log { + QTime timeStamp; + int logLevel; + QString port; + QString message; + }; + QVector logs_; + LogLevel currentLevel_{kInfo}; +}; +#endif + diff --git a/client/logswindow.cpp b/client/logswindow.cpp new file mode 100644 index 0000000..f9cfd04 --- /dev/null +++ b/client/logswindow.cpp @@ -0,0 +1,114 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#include "logswindow.h" + +#include "logsmodel.h" +#include "modeltest.h" + +#include +#include + +LogsWindow::LogsWindow(LogsModel *model, QWidget *parent) + : QWidget(parent) +{ + setupUi(this); + logs->setModel(model); + autoScroll->setChecked(true); + + logs->verticalHeader()->setHighlightSections(false); + logs->verticalHeader()->setDefaultSectionSize( + logs->verticalHeader()->minimumSectionSize()); + logs->setShowGrid(false); + logs->setAlternatingRowColors(true); + logs->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); + + parentDock_ = qobject_cast(parent); + windowTitle_ = parentDock_->windowTitle(); + + connect(level, SIGNAL(currentIndexChanged(int)), + model, SLOT(setLogLevel(int))); + connect(clear, SIGNAL(clicked()), model, SLOT(clear())); + + connect(parentDock_, SIGNAL(visibilityChanged(bool)), + SLOT(when_visibilityChanged(bool))); + connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + SLOT(when_rowsInserted(const QModelIndex&, int, int))); + +#if defined(QT_NO_DEBUG) || QT_VERSION < 0x050700 + logsModelTest_ = nullptr; +#else + logsModelTest_ = new ModelTest(model); +#endif +} + +LogsWindow::~LogsWindow() +{ + delete logsModelTest_; +} + +void LogsWindow::when_visibilityChanged(bool visible) +{ + if (visible) { + parentDock_->setWindowTitle(windowTitle_); + annotation_.clear(); + } + + isVisible_ = visible; + qDebug("isVisible = %u", isVisible_); +} + +void LogsWindow::when_rowsInserted(const QModelIndex &parent, + int first, int last) +{ + if (isVisible_) + return; + + if (annotation_.contains("Error")) + return; + + for (int i = first; i <= last; i++) { + // FIXME: use a user-role instead, so we don't need to know column and + // have to compare strings? + QString level = logs->model()->data(logs->model()->index(i, 1, parent)) + .toString(); + if (level == "Error") { + annotation_ = QString(" - Error(s)"); + break; // Highest level - no need to look further + } + else if (level == "Warning") { + annotation_ = QString(" - Warning(s)"); + } + } + parentDock_->setWindowTitle(windowTitle_+annotation_); +} + +void LogsWindow::on_autoScroll_toggled(bool checked) +{ + if (checked) { + connect(logs->model(), + SIGNAL(rowsInserted(const QModelIndex&, int, int)), + logs, SLOT(scrollToBottom())); + } + else { + disconnect(logs->model(), + SIGNAL(rowsInserted(const QModelIndex&, int, int)), + logs, SLOT(scrollToBottom())); + } +} diff --git a/client/logswindow.h b/client/logswindow.h new file mode 100644 index 0000000..ee3d59f --- /dev/null +++ b/client/logswindow.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _LOGS_WINDOW_H +#define _LOGS_WINDOW_H + +#include "ui_logswindow.h" + +class LogsModel; +class QDockWidget; +class QShowEvent; + +class LogsWindow: public QWidget, private Ui::LogsWindow +{ + Q_OBJECT +public: + LogsWindow(LogsModel *model, QWidget *parent = 0); + ~LogsWindow(); + +private slots: + void when_visibilityChanged(bool visible); + void when_rowsInserted(const QModelIndex &parent, int first, int last); + void on_autoScroll_toggled(bool checked); + +private: + QDockWidget *parentDock_; + QString windowTitle_; + QString annotation_; + bool isVisible_{false}; + QObject *logsModelTest_; +}; + +#endif + diff --git a/client/logswindow.ui b/client/logswindow.ui new file mode 100644 index 0000000..cabff41 --- /dev/null +++ b/client/logswindow.ui @@ -0,0 +1,137 @@ + + + LogsWindow + + + + 0 + 0 + 693 + 300 + + + + Form + + + + + + QFrame::Panel + + + QFrame::Raised + + + + + + Level + + + level + + + + + + + Log Level + + + Select the desired logging level + + + + Info + + + + + Warning + + + + + Error + + + + + + + + Qt::Vertical + + + + + + + Auto Scroll + + + + + + + Qt::Horizontal + + + + 429 + 20 + + + + + + + + Clear Logs + + + Clear Logs + + + Clear + + + + :/icons/portstats_clear_all.png:/icons/portstats_clear_all.png + + + + + + + + + + Ostinato application logs are displayed here + + + QAbstractItemView::SelectRows + + + + + + + + XTableView + QTableView +
xtableview.h
+
+
+ + level + autoScroll + clear + logs + + + + + +
diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 8138657..da375e0 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -24,6 +24,8 @@ along with this program. If not, see #endif #include "jumpurl.h" +#include "logsmodel.h" +#include "logswindow.h" #include "params.h" #include "portgrouplist.h" #include "portstatswindow.h" @@ -56,6 +58,7 @@ extern const char* version; extern const char* revision; PortGroupList *pgl; +LogsModel *appLogs; MainWindow::MainWindow(QWidget *parent) : QMainWindow (parent) @@ -89,24 +92,39 @@ MainWindow::MainWindow(QWidget *parent) localServer_ = NULL; pgl = new PortGroupList; + appLogs = new LogsModel(this); portsWindow = new PortsWindow(pgl, this); statsWindow = new PortStatsWindow(pgl, this); + portsDock = new QDockWidget(tr("Ports and Streams"), this); portsDock->setObjectName("portsDock"); portsDock->setFeatures( portsDock->features() & ~QDockWidget::DockWidgetClosable); + statsDock = new QDockWidget(tr("Port Statistics"), this); statsDock->setObjectName("statsDock"); statsDock->setFeatures( statsDock->features() & ~QDockWidget::DockWidgetClosable); + logsDock_ = new QDockWidget(tr("Logs"), this); + logsDock_->setObjectName("logsDock"); + logsDock_->setFeatures( + logsDock_->features() & ~QDockWidget::DockWidgetClosable); + logsWindow_ = new LogsWindow(appLogs, logsDock_); + setupUi(this); menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions()); statsDock->setWidget(statsWindow); addDockWidget(Qt::BottomDockWidgetArea, statsDock); + logsDock_->setWidget(logsWindow_); + addDockWidget(Qt::BottomDockWidgetArea, logsDock_); + tabifyDockWidget(statsDock, logsDock_); + statsDock->show(); + statsDock->raise(); + portsDock->setWidget(portsWindow); addDockWidget(Qt::TopDockWidgetArea, portsDock); @@ -334,6 +352,11 @@ void MainWindow::on_actionHelpOnline_triggered() QDesktopServices::openUrl(QUrl(jumpUrl("help", "app", "menu"))); } +void MainWindow::on_actionDonate_triggered() +{ + QDesktopServices::openUrl(QUrl(jumpUrl("donate", "app", "menu"))); +} + void MainWindow::on_actionHelpAbout_triggered() { QDialog *aboutDialog = new QDialog; diff --git a/client/mainwindow.h b/client/mainwindow.h index 8f1f22b..c8a47f0 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -24,6 +24,7 @@ along with this program. If not, see #include #include +class LogsWindow; class PortsWindow; class PortStatsWindow; @@ -41,8 +42,10 @@ private: QProcess *localServer_; PortsWindow *portsWindow; PortStatsWindow *statsWindow; + LogsWindow *logsWindow_; QDockWidget *portsDock; QDockWidget *statsDock; + QDockWidget *logsDock_; QRect defaultGeometry_; QByteArray defaultLayout_; @@ -57,6 +60,7 @@ public slots: void on_actionPreferences_triggered(); void on_actionViewRestoreDefaults_triggered(); void on_actionHelpOnline_triggered(); + void on_actionDonate_triggered(); void on_actionHelpAbout_triggered(); private slots: diff --git a/client/mainwindow.ui b/client/mainwindow.ui index 141c193..f6c195f 100644 --- a/client/mainwindow.ui +++ b/client/mainwindow.ui @@ -1,3 +1,4 @@ + MainWindow @@ -33,6 +34,8 @@ + + @@ -111,6 +114,14 @@ Help (Online) + + + :/icons/donate.png + + + Donate + + diff --git a/client/ostinato.pro b/client/ostinato.pro index b86d5a8..77aaa44 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -41,6 +41,8 @@ HEADERS += \ deviceswidget.h \ dumpview.h \ hexlineedit.h \ + logsmodel.h \ + logswindow.h \ mainwindow.h \ ndpstatusmodel.h \ packetmodel.h \ @@ -68,6 +70,7 @@ FORMS += \ about.ui \ devicegroupdialog.ui \ deviceswidget.ui \ + logswindow.ui \ mainwindow.ui \ portconfigdialog.ui \ portstatsfilter.ui \ @@ -87,6 +90,8 @@ SOURCES += \ dumpview.cpp \ stream.cpp \ hexlineedit.cpp \ + logsmodel.cpp \ + logswindow.cpp \ main.cpp \ mainwindow.cpp \ ndpstatusmodel.cpp \ diff --git a/client/ostinato.qrc b/client/ostinato.qrc index b0d57b7..2e9b98e 100644 --- a/client/ostinato.qrc +++ b/client/ostinato.qrc @@ -1,48 +1,55 @@ - - icons/about.png - icons/add.png - icons/arrow_down.png - icons/arrow_left.png - icons/arrow_right.png - icons/arrow_up.png - icons/bullet_error.png - icons/bullet_green.png - icons/bullet_orange.png - icons/bullet_red.png - icons/bullet_white.png - icons/bullet_yellow.png - icons/control_play.png - icons/control_stop.png - icons/deco_exclusive.png - icons/delete.png - icons/devicegroup_add.png - icons/devicegroup_delete.png - icons/devicegroup_edit.png - icons/exit.png - icons/gaps.png - icons/help.png - icons/logo.png - icons/magnifier.png - icons/name.png - icons/neighbor_clear.png - icons/neighbor_resolve.png - icons/portgroup_add.png - icons/portgroup_connect.png - icons/portgroup_delete.png - icons/portgroup_disconnect.png - icons/portstats_clear.png - icons/portstats_clear_all.png - icons/portstats_filter.png - icons/preferences.png - icons/qt.png - icons/refresh.png - icons/sound_mute.png - icons/sound_none.png - icons/stream_add.png - icons/stream_delete.png - icons/stream_duplicate.png - icons/stream_edit.png - icons/stream_stats.png - + + icons/about.png + icons/add.png + icons/arrow_down.png + icons/arrow_left.png + icons/arrow_right.png + icons/arrow_up.png + icons/bullet_error.png + icons/bullet_green.png + icons/bullet_orange.png + icons/bullet_red.png + icons/bullet_white.png + icons/bullet_yellow.png + icons/control_play.png + icons/control_stop.png + icons/delete.png + icons/devicegroup_add.png + icons/devicegroup_delete.png + icons/devicegroup_edit.png + icons/donate.png + icons/exit.png + icons/frag_capture.png + icons/frag_exclusive.png + icons/frag_link_down.png + icons/frag_link_unknown.png + icons/frag_link_up.png + icons/frag_transmit.png + icons/gaps.png + icons/help.png + icons/logo.png + icons/magnifier.png + icons/name.png + icons/neighbor_clear.png + icons/neighbor_resolve.png + icons/portgroup_add.png + icons/portgroup_connect.png + icons/portgroup_delete.png + icons/portgroup_disconnect.png + icons/portstats_clear.png + icons/portstats_clear_all.png + icons/portstats_filter.png + icons/preferences.png + icons/qt.png + icons/refresh.png + icons/sound_mute.png + icons/sound_none.png + icons/stream_add.png + icons/stream_delete.png + icons/stream_duplicate.png + icons/stream_edit.png + icons/stream_stats.png + icons/transmit_on.png + diff --git a/client/port.cpp b/client/port.cpp index e9da599..c7154e7 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -531,7 +531,9 @@ void Port::updateStats(OstProto::PortStats *portStats) oldState = stats.state(); stats.MergeFrom(*portStats); - if (oldState.link_state() != stats.state().link_state()) + if ((oldState.link_state() != stats.state().link_state()) + || (oldState.is_transmit_on() != stats.state().is_transmit_on()) + || (oldState.is_capture_on() != stats.state().is_capture_on())) { qDebug("portstate changed"); emit portDataChanged(mPortGroupId, mPortId); diff --git a/client/port.h b/client/port.h index d794480..9a7d7e6 100644 --- a/client/port.h +++ b/client/port.h @@ -130,6 +130,10 @@ public: } OstProto::LinkState linkState() { return stats.state().link_state(); } + bool isTransmitting() + { return stats.state().is_transmit_on(); } + bool isCapturing() + { return stats.state().is_capture_on(); } OstProto::PortStats getStats() { return stats; } QTemporaryFile* getCaptureFile() diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 4c7180f..b8df277 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -20,6 +20,7 @@ along with this program. If not, see #include "portgroup.h" #include "jumpurl.h" +#include "log.h" #include "settings.h" #include "emulproto.pb.h" @@ -166,6 +167,7 @@ void PortGroup::on_rpcChannel_connected() new OstProto::VersionCompatibility; qDebug("connected\n"); + logInfo(id(), "PortGroup connected"); emit portGroupDataChanged(mPortGroupId); reconnectAfter = kMinReconnectWaitTime; @@ -193,12 +195,16 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), QString("checkVersion RPC failed: %1") + .arg(controller->ErrorString())); goto _error_exit; } if (verCompat->result() == OstProto::VersionCompatibility::kIncompatible) { qWarning("incompatible version %s (%s)", version, qPrintable(QString::fromStdString(verCompat->notes()))); + logError(id(), QString("checkVersion failed: %1") + .arg(QString::fromStdString(verCompat->notes()))); compat = kIncompatible; emit portGroupDataChanged(mPortGroupId); @@ -236,6 +242,7 @@ _error_exit: void PortGroup::on_rpcChannel_disconnected() { qDebug("disconnected\n"); + logError(id(), "PortGroup disconnected"); emit portListAboutToBeChanged(mPortGroupId); while (!mPorts.isEmpty()) @@ -250,6 +257,8 @@ void PortGroup::on_rpcChannel_disconnected() if (reconnect) { qDebug("starting reconnect timer for %d ms ...", reconnectAfter); + logInfo(id(), QString("Reconnect attempt after %1s") + .arg(double(reconnectAfter)/1000.0)); reconnectTimer->start(reconnectAfter); } } @@ -266,6 +275,8 @@ void PortGroup::on_rpcChannel_error(QAbstractSocket::SocketError socketError) if ((rpcChannel->state() == QAbstractSocket::UnconnectedState) && reconnect) { qDebug("starting reconnect timer for %d ms...", reconnectAfter); + logInfo(id(), QString("Reconnect attempt after %1s") + .arg(double(reconnectAfter)/1000.0)); reconnectTimer->start(reconnectAfter); } } @@ -296,6 +307,10 @@ void PortGroup::on_rpcChannel_notification(int notifType, qWarning("notif(portConfigChanged) has an empty port_id_list"); return; } + for(int i=0; i < notif->port_id_list().port_id_size(); i++) { + logInfo(id(), notif->port_id_list().port_id(i).id(), + QString("Port configuration changed notification")); + } OstProto::PortIdList *portIdList = new OstProto::PortIdList; OstProto::PortConfigList *portConfigList = @@ -318,6 +333,7 @@ void PortGroup::when_portListChanged(quint32 /*portGroupId*/) { if (state() == QAbstractSocket::ConnectedState && numPorts() <= 0) { + logError(id(), QString("No ports in portlist")); QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); msgBox.setTextFormat(Qt::RichText); @@ -348,6 +364,8 @@ void PortGroup::processPortIdList(PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), QString("getPortIdList RPC failed: %1") + .arg(controller->ErrorString())); goto _error_exit; } @@ -403,6 +421,8 @@ void PortGroup::processPortConfigList(PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), QString("getPortConfig RPC failed: %1") + .arg(controller->ErrorString())); goto _error_exit; } @@ -453,6 +473,9 @@ void PortGroup::processPortConfigList(PbRpcController *controller) if (!port->userName().isEmpty() // rsvd? && port->userName() != myself) // by someone else? { + logWarn(id(), j, QString("Port is reserved by %1. " + "Skipping reconfiguration") + .arg(port->userName())); QString warning = QString("%1 - %2: %3 is reserved by %4.\n\n" "Port will not be reconfigured.") @@ -460,8 +483,8 @@ void PortGroup::processPortConfigList(PbRpcController *controller) .arg(j) .arg(port->userAlias()) .arg(port->userName()); - QMessageBox::warning(NULL, tr("Open Session"), warning); qWarning("%s", qPrintable(warning)); + QMessageBox::warning(NULL, tr("Open Session"), warning); continue; } atConnectPortConfig_[j] = pc; @@ -509,6 +532,8 @@ void PortGroup::when_configApply(int portIndex) bool refreshReqd = false; qDebug("applying 'deleted deviceGroups' ..."); + logInfo(id(), mPorts[portIndex]->id(), + QString("Deleting old DeviceGroups")); deviceGroupIdList = new OstProto::DeviceGroupIdList; deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); mPorts[portIndex]->getDeletedDeviceGroupsSinceLastSync(*deviceGroupIdList); @@ -524,6 +549,8 @@ void PortGroup::when_configApply(int portIndex) delete deviceGroupIdList; qDebug("applying 'new deviceGroups' ..."); + logInfo(id(), mPorts[portIndex]->id(), + QString("Creating new DeviceGroups")); deviceGroupIdList = new OstProto::DeviceGroupIdList; deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); mPorts[portIndex]->getNewDeviceGroupsSinceLastSync(*deviceGroupIdList); @@ -539,6 +566,8 @@ void PortGroup::when_configApply(int portIndex) delete deviceGroupIdList; qDebug("applying 'modified deviceGroups' ..."); + logInfo(id(), mPorts[portIndex]->id(), + QString("Modifying changed DeviceGroups")); deviceGroupConfigList = new OstProto::DeviceGroupConfigList; deviceGroupConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); mPorts[portIndex]->getModifiedDeviceGroupsSinceLastSync( @@ -561,6 +590,7 @@ void PortGroup::when_configApply(int portIndex) // Update/Sync Streams // qDebug("applying 'deleted streams' ..."); + logInfo(id(), mPorts[portIndex]->id(), QString("Deleting old Streams")); streamIdList = new OstProto::StreamIdList; ack = new OstProto::Ack; controller = new PbRpcController(streamIdList, ack); @@ -572,6 +602,7 @@ void PortGroup::when_configApply(int portIndex) NewCallback(this, &PortGroup::processDeleteStreamAck, controller)); qDebug("applying 'new streams' ..."); + logInfo(id(), mPorts[portIndex]->id(), QString("Creating new Streams")); streamIdList = new OstProto::StreamIdList; ack = new OstProto::Ack; controller = new PbRpcController(streamIdList, ack); @@ -583,6 +614,8 @@ void PortGroup::when_configApply(int portIndex) NewCallback(this, &PortGroup::processAddStreamAck, controller)); qDebug("applying 'modified streams' ..."); + logInfo(id(), mPorts[portIndex]->id(), + QString("Modifying changed Streams")); streamConfigList = new OstProto::StreamConfigList; ack = new OstProto::Ack; controller = new PbRpcController(streamConfigList, ack); @@ -633,6 +666,7 @@ void PortGroup::processModifyStreamAck(int portIndex, qDebug("In %s", __FUNCTION__); qDebug("apply completed"); + logInfo(id(), mPorts[portIndex]->id(), QString("All port changes applied")); mPorts[portIndex]->when_syncComplete(); mainWindow->setEnabled(true); @@ -682,6 +716,9 @@ void PortGroup::processDeviceList(int portIndex, PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), mPorts[portIndex]->id(), + QString("getDeviceList RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -716,6 +753,9 @@ void PortGroup::processDeviceNeighbors( { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), mPorts[portIndex]->id(), + QString("getPortIdList RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -769,6 +809,8 @@ void PortGroup::processModifyPortAck(bool restoreUi,PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), QString("modifyPort RPC failed: %1") + .arg(controller->ErrorString())); } if (restoreUi) { @@ -789,6 +831,8 @@ void PortGroup::processUpdatedPortConfig(PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), QString("getPortConfig RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -837,6 +881,9 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), mPorts[portIndex]->id(), + QString("geStreamIdList RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -1051,6 +1098,9 @@ void PortGroup::processStreamConfigList(int portIndex, { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), mPorts[portIndex]->id(), + QString("getStreamConfigList RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -1121,6 +1171,9 @@ void PortGroup::processDeviceGroupIdList( { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), mPorts[portIndex]->id(), + QString("getDeviceGroupIdList RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -1215,6 +1268,9 @@ void PortGroup::processDeviceGroupConfigList(int portIndex, { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(controller->ErrorString())); + logError(id(), mPorts[portIndex]->id(), + QString("getDeviceGroupConfigList RPC failed: %1") + .arg(controller->ErrorString())); goto _exit; } @@ -1436,14 +1492,17 @@ void PortGroup::processViewCaptureAck(PbRpcController *controller) if (!QFile::exists(viewer)) { + logError(QString("Wireshark does not exist at %1").arg(viewer)); QMessageBox::warning(NULL, "Can't find Wireshark", viewer + QString(" does not exist!\n\nPlease correct the path" " to Wireshark in the Preferences.")); goto _exit; } - if (!QProcess::startDetached(viewer, QStringList() << capFile->fileName())) + if (!QProcess::startDetached(viewer, QStringList() << capFile->fileName())) { qDebug("Failed starting Wireshark"); + logError(QString("Failed to start %1").arg(viewer)); + } _exit: delete controller; @@ -1553,6 +1612,8 @@ void PortGroup::processPortStatsList() { qDebug("%s: rpc failed(%s)", __FUNCTION__, qPrintable(statsController->ErrorString())); + logError(id(), QString("getPortStatsList RPC failed: %1") + .arg(statsController->ErrorString())); goto _error_exit; } diff --git a/client/portmodel.cpp b/client/portmodel.cpp index ae3d81e..da371d2 100644 --- a/client/portmodel.cpp +++ b/client/portmodel.cpp @@ -22,6 +22,7 @@ along with this program. If not, see #include #include +#include #if 0 #define DBG0(x) qDebug(x) @@ -35,23 +36,6 @@ PortModel::PortModel(PortGroupList *p, QObject *parent) : QAbstractItemModel(parent) { pgl = p; - - portIconFactory[OstProto::LinkStateUnknown][false] = - QIcon(":/icons/bullet_white.png"); - portIconFactory[OstProto::LinkStateDown][false] = - QIcon(":/icons/bullet_red.png"); - portIconFactory[OstProto::LinkStateUp][false] = - QIcon(":/icons/bullet_green.png"); - - for (int linkState = 0; linkState < kLinkStatesCount; linkState++) - { - QPixmap pixmap(":/icons/deco_exclusive.png"); - QPainter painter(&pixmap); - QIcon icon = portIconFactory[linkState][false]; - - painter.drawPixmap(0, 0, icon.pixmap(QSize(32,32))); - portIconFactory[linkState][true] = QIcon(pixmap); - } } int PortModel::rowCount(const QModelIndex &parent) const @@ -185,7 +169,11 @@ QVariant PortModel::data(const QModelIndex &index, int role) const } else if (role == Qt::DecorationRole) { - return portIconFactory[port->linkState()][port->hasExclusiveControl()]; + return statusIcon( + port->linkState(), + port->hasExclusiveControl(), + port->isTransmitting(), + port->isCapturing()); } else if (role == Qt::ForegroundRole) { @@ -294,7 +282,50 @@ quint32 PortModel::portId(const QModelIndex& index) return (index.internalId()) & 0xFFFF; } +QPixmap PortModel::statusIcon( + int linkState, bool exclusive, bool transmit, bool capture) const +{ + QPixmap pixmap; + QString key = QString("$ost:portStatusIcon:%1:%2:%3:%4") + .arg(linkState).arg(exclusive).arg(transmit).arg(capture); + if (QPixmapCache::find(key, pixmap)) + return pixmap; + + static int sz = QPixmap(":/icons/frag_link_up.png").width(); + + // All frag_* icons must be of same size and can be overlayed + // on top of each other; assume square icons + + pixmap = QPixmap(sz, sz); + pixmap.fill(Qt::transparent); + + QPainter painter(&pixmap); + + switch (linkState) { + case OstProto::LinkStateUp: + painter.drawPixmap(0, 0, QPixmap(":/icons/frag_link_up.png")); + break; + case OstProto::LinkStateDown: + painter.drawPixmap(0, 0, QPixmap(":/icons/frag_link_down.png")); + break; + case OstProto::LinkStateUnknown: + painter.drawPixmap(0, 0, QPixmap(":/icons/frag_link_unknown.png")); + break; + } + + if (exclusive) + painter.drawPixmap(0, 0, QPixmap(":/icons/frag_exclusive.png")); + + if (transmit) + painter.drawPixmap(0, 0, QPixmap(":/icons/frag_transmit.png")); + + if (capture) + painter.drawPixmap(0, 0, QPixmap(":/icons/frag_capture.png")); + + QPixmapCache::insert(key, pixmap); + return pixmap; +} // ---------------------------------------------- // Slots diff --git a/client/portmodel.h b/client/portmodel.h index 4464fdf..0bfdf6a 100644 --- a/client/portmodel.h +++ b/client/portmodel.h @@ -50,12 +50,6 @@ public: quint32 portGroupId(const QModelIndex& index); quint32 portId(const QModelIndex& index); -private: - PortGroupList *pgl; - static const int kLinkStatesCount = 3; - static const int kExclusiveStatesCount = 2; - QIcon portIconFactory[kLinkStatesCount][kExclusiveStatesCount]; - private slots: // FIXME: these are invoked from outside - how come they are "private"? void when_portGroupDataChanged(int portGroupId, int portId); @@ -71,6 +65,12 @@ private slots: void triggerLayoutAboutToBeChanged(); void triggerLayoutChanged(); #endif + +private: + QPixmap statusIcon(int linkState, bool exclusive, + bool transmit, bool capture) const; + + PortGroupList *pgl; }; #endif diff --git a/client/portstatsmodel.cpp b/client/portstatsmodel.cpp index b91e763..1dcf197 100644 --- a/client/portstatsmodel.cpp +++ b/client/portstatsmodel.cpp @@ -20,6 +20,8 @@ along with this program. If not, see #include "portstatsmodel.h" #include "portgrouplist.h" +#include +#include #include enum { @@ -94,15 +96,12 @@ void PortStatsModel::getDomainIndexes(const QModelIndex &index, QVariant PortStatsModel::data(const QModelIndex &index, int role) const { - uint pgidx, pidx; - int row; - // Check for a valid index if (!index.isValid()) return QVariant(); // Check for row/column limits - row = index.row(); + int row = index.row(); if (row >= e_STAT_MAX) return QVariant(); @@ -112,15 +111,22 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const if (index.column() >= (numPorts.last())) return QVariant(); + if (role == Qt::TextAlignmentRole) + { + if (row >= e_STATISTICS_START && row <= e_STATISTICS_END) + return Qt::AlignRight; // right-align numbers + else + return Qt::AlignHCenter; // center-align everything else + } + + uint pgidx, pidx; getDomainIndexes(index, pgidx, pidx); + OstProto::PortStats stats = pgl->mPortGroups.at(pgidx) + ->mPorts[pidx]->getStats(); // Check role if (role == Qt::DisplayRole) { - OstProto::PortStats stats; - - stats = pgl->mPortGroups.at(pgidx)->mPorts[pidx]->getStats(); - switch(row) { // Info @@ -128,14 +134,8 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const return pgl->mPortGroups.at(pgidx)->mPorts[pidx]->userName(); // States - case e_LINK_STATE: - return LinkStateName.at(stats.state().link_state()); - - case e_TRANSMIT_STATE: - return BoolStateName.at(stats.state().is_transmit_on()); - - case e_CAPTURE_STATE: - return BoolStateName.at(stats.state().is_capture_on()); + case e_COMBO_STATE: + return QVariant(); // Statistics case e_STAT_FRAMES_RCVD: @@ -201,12 +201,47 @@ QVariant PortStatsModel::data(const QModelIndex &index, int role) const return 0; } } - else if (role == Qt::TextAlignmentRole) + else if (role == Qt::DecorationRole) { - if (row >= e_STATISTICS_START && row <= e_STATISTICS_END) - return Qt::AlignRight; // right-align numbers + if (row == e_COMBO_STATE) + return statusIcons( + stats.state().link_state(), + stats.state().is_transmit_on(), + stats.state().is_capture_on()); else - return Qt::AlignHCenter; // center-align everything else + return QVariant(); + } + else if (role == Qt::ToolTipRole) + { + if (row == e_COMBO_STATE) { + QString linkIcon; + switch (stats.state().link_state()) { + case OstProto::LinkStateUp: + linkIcon = ":/icons/bullet_green.png"; + break; + case OstProto::LinkStateDown: + linkIcon = ":/icons/bullet_red.png"; + break; + case OstProto::LinkStateUnknown: + linkIcon = ":/icons/bullet_white.png"; + break; + } + // FIXME: Ideally, the text should be vertically centered wrt icon + // but style='vertical-align:middle for the img tag doesn't work + QString tooltip = QString(" Link %2") + .arg(linkIcon) + .arg(LinkStateName.at( + stats.state().link_state())); + if (stats.state().is_transmit_on()) + tooltip.prepend("" + " Transmit On
"); + if (stats.state().is_capture_on()) + tooltip.append("
" + " Capture On"); + return tooltip; + } + else + return QVariant(); } else return QVariant(); @@ -351,3 +386,49 @@ void PortStatsModel::when_portGroup_stats_update(quint32 /*portGroupId*/) emit dataChanged(topLeft, bottomRight); } + +QPixmap PortStatsModel::statusIcons( + int linkState, bool transmit, bool capture) const +{ + QPixmap pixmap; + QString key = QString("$ost:statusList:%1:%2:%3") + .arg(linkState).arg(transmit).arg(capture); + + if (QPixmapCache::find(key, pixmap)) + return pixmap; + + static int sz = QPixmap(":/icons/transmit_on.png").width(); + + // Assume all icons are of same size and are square + QPixmap blank(sz, sz); + blank.fill(Qt::transparent); + + pixmap = QPixmap(sz*3, sz); + pixmap.fill(Qt::transparent); + + QPainter painter(&pixmap); + + painter.drawPixmap(0, 0, + transmit ? QPixmap(":/icons/transmit_on.png") : blank); + + switch (linkState) { + case OstProto::LinkStateUp: + painter.drawPixmap(sz, 0, QPixmap(":/icons/bullet_green.png")); + break; + case OstProto::LinkStateDown: + painter.drawPixmap(sz, 0, QPixmap(":/icons/bullet_red.png")); + break; + case OstProto::LinkStateUnknown: + painter.drawPixmap(sz, 0, QPixmap(":/icons/bullet_white.png")); + break; + default: + painter.drawPixmap(sz, 0, blank); + } + + painter.drawPixmap(sz*2, 0, + capture ? QPixmap(":/icons/sound_none.png") : blank); + + + QPixmapCache::insert(key, pixmap); + return pixmap; +} diff --git a/client/portstatsmodel.h b/client/portstatsmodel.h index b37e450..e4dbaa2 100644 --- a/client/portstatsmodel.h +++ b/client/portstatsmodel.h @@ -36,11 +36,9 @@ typedef enum { // State e_STATE_START, - e_LINK_STATE = e_STATE_START, - e_TRANSMIT_STATE, - e_CAPTURE_STATE, + e_COMBO_STATE = e_STATE_START, - e_STATE_END = e_CAPTURE_STATE, + e_STATE_END = e_COMBO_STATE, // Statistics e_STATISTICS_START, @@ -77,9 +75,7 @@ typedef enum { static QStringList PortStatName = (QStringList() << "User" - << "Link State" - << "Transmit State" - << "Capture State" + << "Status" << "Frames Received" << "Frames Sent" @@ -110,11 +106,6 @@ static QStringList LinkStateName = (QStringList() << "Up" ); -static QStringList BoolStateName = (QStringList() - << "Off" - << "On" -); - class PortGroupList; class PortStatsModel : public QAbstractTableModel @@ -160,6 +151,7 @@ class PortStatsModel : public QAbstractTableModel void getDomainIndexes(const QModelIndex &index, uint &portGroupIdx, uint &portIdx) const; + QPixmap statusIcons(int linkState, bool transmit, bool capture) const; }; diff --git a/client/portstatswindow.cpp b/client/portstatswindow.cpp index 13b9eeb..aff6a83 100644 --- a/client/portstatswindow.cpp +++ b/client/portstatswindow.cpp @@ -20,6 +20,7 @@ along with this program. If not, see #include "portstatswindow.h" +#include "icononlydelegate.h" #include "portstatsfilterdialog.h" #include "portstatsmodel.h" #include "portstatsproxymodel.h" @@ -53,6 +54,24 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent) tvPortStats->verticalHeader()->setHighlightSections(false); tvPortStats->verticalHeader()->setDefaultSectionSize( tvPortStats->verticalHeader()->minimumSectionSize()); + + statusDelegate = new IconOnlyDelegate(this); +#if 0 + // XXX: Ideally we should use this, but it doesn't work because in + // this constructor, the port model is empty and model->index() returns + // an invalid index ... + tvPortStats->setItemDelegateForRow( + proxyStatsModel ? + proxyStatsModel->mapFromSource(model->index(e_COMBO_STATE, 0)) + .row() : + e_COMBO_STATE, + statusDelegate); +#else + // ... so we use this hard-coded hack + tvPortStats->setItemDelegateForRow( + proxyStatsModel ? e_COMBO_STATE-1 : e_COMBO_STATE, + statusDelegate); +#endif connect(tvPortStats->selectionModel(), SIGNAL(selectionChanged( @@ -65,6 +84,7 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent) PortStatsWindow::~PortStatsWindow() { delete proxyStatsModel; + delete statusDelegate; } /* ------------- SLOTS (public) -------------- */ @@ -205,6 +225,13 @@ void PortStatsWindow::on_tbResolveNeighbors_clicked() { pgl->portGroupByIndex(portList.at(i).portGroupId). resolveDeviceNeighbors(&portList[i].portList); + + // Update device info for the just processed portgroup + for (int j = 0; j < portList[i].portList.size(); j++) + { + pgl->portGroupByIndex(portList.at(i).portGroupId). + getDeviceInfo(portList[i].portList[j]); + } } } @@ -220,6 +247,13 @@ void PortStatsWindow::on_tbClearNeighbors_clicked() { pgl->portGroupByIndex(portList.at(i).portGroupId). clearDeviceNeighbors(&portList[i].portList); + + // Update device info for the just processed portgroup + for (int j = 0; j < portList[i].portList.size(); j++) + { + pgl->portGroupByIndex(portList.at(i).portGroupId). + getDeviceInfo(portList[i].portList[j]); + } } } diff --git a/client/portstatswindow.h b/client/portstatswindow.h index 6efb263..a7b8fe3 100644 --- a/client/portstatswindow.h +++ b/client/portstatswindow.h @@ -27,6 +27,7 @@ along with this program. If not, see #include "portstatsmodel.h" class QSortFilterProxyModel; +class QStyledItemDelegate; class PortStatsWindow : public QWidget, public Ui::PortStatsWindow { @@ -63,6 +64,7 @@ private: PortGroupList *pgl; PortStatsModel *model; QSortFilterProxyModel *proxyStatsModel; + QStyledItemDelegate *statusDelegate; QModelIndexList selectedColumns; }; diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 4ca85cb..6fe781b 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -28,6 +28,8 @@ along with this program. If not, see #include "fileformat.pb.h" +#include "xqlocale.h" + #include #include #include @@ -435,7 +437,7 @@ void PortsWindow::on_averagePacketsPerSec_editingFinished() Q_ASSERT(plm->isPort(current)); bool isOk; - double pps = QLocale().toDouble(averagePacketsPerSec->text(), &isOk); + double pps = XLocale().toDouble(averagePacketsPerSec->text(), &isOk); plm->port(current).setAveragePacketRate(pps); } @@ -450,7 +452,7 @@ void PortsWindow::on_averageBitsPerSec_editingFinished() Q_ASSERT(plm->isPort(current)); bool isOk; - double bps = QLocale().toDouble(averageBitsPerSec->text(), &isOk); + double bps = XLocale().toDouble(averageBitsPerSec->text(), &isOk); plm->port(current).setAverageBitRate(bps); } diff --git a/client/portswindow.ui b/client/portswindow.ui index f383271..6a3c0ca 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -57,7 +57,7 @@ <p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> <p>To create a stream, select the port on which you want to send packets.</p> <hr/> -<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://jump.ostinato.org/noports">Get Help!</a></p> +<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="https://jump.ostinato.org/noports">Get Help!</a></p> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -96,7 +96,7 @@ <p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> <p>To create a stream, select the port on which you want to send packets. </p> <hr/> -<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://jump.ostinato.org/noports">Get Help!</a></p> +<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="https://jump.ostinato.org/noports">Get Help!</a></p> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index 7b6a209..a38a77d 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -30,6 +30,8 @@ along with this program. If not, see #include "../common/protocolmanager.h" #include "../common/protocolwidgetfactory.h" +#include "xqlocale.h" + #include #include @@ -1124,9 +1126,9 @@ void StreamConfigDialog::StoreCurrentStream() pStream->setNumBursts(leNumBursts->text().toULong(&isOk)); pStream->setBurstSize(lePacketsPerBurst->text().toULong(&isOk)); pStream->setPacketRate( - QLocale().toDouble(lePacketsPerSec->text(), &isOk)); + XLocale().toDouble(lePacketsPerSec->text(), &isOk)); pStream->setBurstRate( - QLocale().toDouble(leBurstsPerSec->text(), &isOk)); + XLocale().toDouble(leBurstsPerSec->text(), &isOk)); } } @@ -1172,7 +1174,7 @@ void StreamConfigDialog::on_lePacketsPerSec_textChanged(const QString &text) if (rbSendPackets->isChecked()) { - double pktsPerSec = QLocale().toDouble(text, &isOk); + double pktsPerSec = XLocale().toDouble(text, &isOk); double bitsPerSec = pktsPerSec * double((frameLen+kEthFrameOverHead)*8); if (rbPacketsPerSec->isChecked()) @@ -1197,7 +1199,7 @@ void StreamConfigDialog::on_leBurstsPerSec_textChanged(const QString &text) if (rbSendBursts->isChecked()) { - double burstsPerSec = QLocale().toDouble(text, &isOk); + double burstsPerSec = XLocale().toDouble(text, &isOk); double bitsPerSec = burstsPerSec * double(burstSize * (frameLen + kEthFrameOverHead) * 8); if (rbBurstsPerSec->isChecked()) @@ -1222,13 +1224,13 @@ void StreamConfigDialog::on_leBitsPerSec_textEdited(const QString &text) if (rbSendPackets->isChecked()) { - double pktsPerSec = QLocale().toDouble(text, &isOk)/ + double pktsPerSec = XLocale().toDouble(text, &isOk)/ double((frameLen+kEthFrameOverHead)*8); lePacketsPerSec->setText(QString("%L1").arg(pktsPerSec, 0, 'f', 4)); } else if (rbSendBursts->isChecked()) { - double burstsPerSec = QLocale().toDouble(text, &isOk)/ + double burstsPerSec = XLocale().toDouble(text, &isOk)/ double(burstSize * (frameLen + kEthFrameOverHead) * 8); leBurstsPerSec->setText(QString("%L1").arg(burstsPerSec, 0, 'f', 4)); } diff --git a/client/xqlocale.h b/client/xqlocale.h new file mode 100644 index 0000000..c313964 --- /dev/null +++ b/client/xqlocale.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2018 Srivats P. + +This file is part of "Ostinato" + +This is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +#ifndef _X_LOCALE_H +#define _X_LOCALE_H + +#include + +class XLocale: public QLocale +{ +public: + double toDouble(const QString &s, bool *ok = Q_NULLPTR) const { + QString s2 = s; + return QLocale::toDouble(s2.remove(groupSeparator()), ok); + } +}; + +#endif + diff --git a/client/xtableview.h b/client/xtableview.h index 296ec54..3de3353 100644 --- a/client/xtableview.h +++ b/client/xtableview.h @@ -22,6 +22,8 @@ along with this program. If not, see #include +#include +#include #include class XTableView : public QTableView @@ -42,6 +44,31 @@ protected: else QTableView::paintEvent(event); } + + virtual void keyPressEvent(QKeyEvent *event) + { + // Copy selection to clipboard (base class copies only current item) + if (event->matches(QKeySequence::Copy) + && selectionBehavior() == SelectRows) { + QString text; + int lastRow = -1; + QModelIndexList selected = selectionModel()->selectedIndexes(); + qSort(selected); + foreach(QModelIndex index, selected) { + if (index.row() != lastRow) { + if (!text.isEmpty()) + text.append("\n"); + } + else + text.append("\t"); + text.append(model()->data(index).toString()); + lastRow = index.row(); + } + qApp->clipboard()->setText(text); + } + else + QTableView::keyPressEvent(event); + } }; #endif diff --git a/common/abstractprotocol.cpp b/common/abstractprotocol.cpp index 2de699b..477a199 100644 --- a/common/abstractprotocol.cpp +++ b/common/abstractprotocol.cpp @@ -931,18 +931,28 @@ quint32 AbstractProtocol::protocolFrameHeaderCksum(int streamIndex, CksumType cksumType, CksumScope cksumScope) const { quint32 sum = 0; - quint16 cksum; + quint32 cksum; AbstractProtocol *p = prev; Q_ASSERT(cksumType == CksumIpPseudo); + // We may have extension headers between us and the IP header - skip 'em while (p) { cksum = p->protocolFrameCksum(streamIndex, cksumType); - sum += (quint16) ~cksum; - qDebug("%s: sum = %u, cksum = %u", __FUNCTION__, sum, cksum); - if (cksumScope == CksumScopeAdjacentProtocol) - goto out; + if (cksum <= 0xFFFF) // protocol has a valid pseudo cksum ie its IP + { + sum += (quint16) ~cksum; + // Ip4/6Protocol::protocolFrameCksum(CksumIpPseudo) only + // counts the src/dst IP (see Note in there) + // Count the payload length and protocolId here + sum += protocolFrameSize(streamIndex) + + protocolFramePayloadSize(streamIndex); + sum += protocolId(ProtocolIdIp); + qDebug("%s: sum = %x, cksum = %x", __FUNCTION__, sum, cksum); + if (cksumScope == CksumScopeAdjacentProtocol) + goto out; + } p = p->prev; } if (parent) diff --git a/common/hexdump.cpp b/common/hexdump.cpp index d8f882f..12709d2 100644 --- a/common/hexdump.cpp +++ b/common/hexdump.cpp @@ -109,8 +109,8 @@ QVariant HexDumpProtocol::fieldData(int index, FieldAttrib attrib, case FieldValue: case FieldTextValue: case FieldFrameValue: - ba.append(QString().fromStdString(data.content())); - if (data.pad_until_end()) + ba.append(data.content().c_str(), data.content().length()); + if (padUntilEnd()) { pad = QByteArray( protocolFrameSize(streamIndex) - ba.size(), '\0'); @@ -144,7 +144,7 @@ QVariant HexDumpProtocol::fieldData(int index, FieldAttrib attrib, switch(attrib) { case FieldValue: - return data.pad_until_end(); + return padUntilEnd(); default: break; } @@ -197,7 +197,7 @@ int HexDumpProtocol::protocolFrameSize(int streamIndex) const { int len = data.content().size(); - if (data.pad_until_end()) + if (padUntilEnd()) { int pad = mpStream->frameLen(streamIndex) - (protocolFrameOffset(streamIndex) @@ -212,3 +212,10 @@ int HexDumpProtocol::protocolFrameSize(int streamIndex) const return len; } +bool HexDumpProtocol::padUntilEnd() const +{ + if (next) + return false; // No padding if we are not the last protocol + + return data.pad_until_end(); +} diff --git a/common/hexdump.h b/common/hexdump.h index 4318192..38fbbf6 100644 --- a/common/hexdump.h +++ b/common/hexdump.h @@ -68,6 +68,8 @@ public: virtual int protocolFrameSize(int streamIndex = 0) const; private: + bool padUntilEnd() const; + OstProto::HexDump data; }; #endif diff --git a/common/ip4.cpp b/common/ip4.cpp index cdeea98..0d4063f 100644 --- a/common/ip4.cpp +++ b/common/ip4.cpp @@ -521,7 +521,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, case FieldValue: case FieldFrameValue: case FieldTextValue: - ba.append(QString().fromStdString(data.options())); + ba.append(data.options().c_str(), data.options().length()); default: break; } @@ -859,14 +859,18 @@ quint32 Ip4Protocol::protocolFrameCksum(int streamIndex, sum += *((quint16*)(p + 14)); // src-ip lo sum += *((quint16*)(p + 16)); // dst-ip hi sum += *((quint16*)(p + 18)); // dst-ip lo - sum += qToBigEndian((quint16) - protocolFramePayloadSize(streamIndex)); // len - sum += qToBigEndian((quint16) *(p + 9)); // proto + + // XXX: payload length and protocol are also part of the + // pseudo cksum but for IPv6 we need to skip extension headers to + // get to them, so these two fields are counted in the + // pseudo cksum in AbstractProtocol::protocolFrameHeaderCksum() + // Although not needed for IPv4 case, we do the same for IPv4 + // also, so that code there is common for IPv4 and IPv6 while(sum>>16) sum = (sum & 0xFFFF) + (sum >> 16); - return ~qFromBigEndian((quint16)sum); + return qFromBigEndian((quint16) ~sum); } default: break; diff --git a/common/ip4pdml.cpp b/common/ip4pdml.cpp index eefbd6e..a47a83c 100644 --- a/common/ip4pdml.cpp +++ b/common/ip4pdml.cpp @@ -63,8 +63,11 @@ void PdmlIp4Protocol::unknownFieldHandler(QString name, int /*pos*/, else if ((name == "ip.options") || attributes.value("show").toString().startsWith("Options")) { - options_ = QByteArray::fromHex( + OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4); + + QByteArray options = QByteArray::fromHex( attributes.value("value").toString().toUtf8()); + ip4->set_options(options.constData(), options.size()); } else if (name == "ip.flags") { @@ -75,7 +78,7 @@ void PdmlIp4Protocol::unknownFieldHandler(QString name, int /*pos*/, } void PdmlIp4Protocol::postProtocolHandler(OstProto::Protocol *pbProto, - OstProto::Stream *stream) + OstProto::Stream* /*stream*/) { OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4); @@ -84,20 +87,5 @@ void PdmlIp4Protocol::postProtocolHandler(OstProto::Protocol *pbProto, ip4->set_is_override_totlen(true); ip4->set_is_override_proto(true); ip4->set_is_override_cksum(true); - - if (options_.size()) - { - OstProto::Protocol *proto = stream->add_protocol(); - - proto->mutable_protocol_id()->set_id( - OstProto::Protocol::kHexDumpFieldNumber); - - OstProto::HexDump *hexDump = proto->MutableExtension(OstProto::hexDump); - - hexDump->mutable_content()->append(options_.constData(), - options_.size()); - hexDump->set_pad_until_end(false); - options_.resize(0); - } } diff --git a/common/ip4pdml.h b/common/ip4pdml.h index 64f818d..2aa3d04 100644 --- a/common/ip4pdml.h +++ b/common/ip4pdml.h @@ -34,8 +34,6 @@ public: OstProto::Stream *stream); protected: PdmlIp4Protocol(); -private: - QByteArray options_; }; #endif diff --git a/common/ip6.cpp b/common/ip6.cpp index 3bbf7d3..6ec1587 100644 --- a/common/ip6.cpp +++ b/common/ip6.cpp @@ -781,13 +781,18 @@ quint32 Ip6Protocol::protocolFrameCksum(int streamIndex, // src-ip, dst-ip for (int i = 8; i < fv.size(); i+=2) sum += *((quint16*)(p + i)); - sum += *((quint16*)(p + 4)); // payload len - sum += qToBigEndian((quint16) *(p + 6)); // proto + + // XXX: payload length and protocol are also part of the + // pseudo cksum but we need to skip extension headers to + // get to them as per RFC 8200 Section 8.1 + // Since we can't traverse beyond our immediate neighboring + // protocol from here, these two fields are counted in the + // pseudo cksum in AbstractProtocol::protocolFrameHeaderCksum() while(sum>>16) sum = (sum & 0xFFFF) + (sum >> 16); - return ~qFromBigEndian((quint16)sum); + return qFromBigEndian((quint16) ~sum); } return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); } diff --git a/common/pcapfileformat.cpp b/common/pcapfileformat.cpp index 1db047d..c6eba2b 100644 --- a/common/pcapfileformat.cpp +++ b/common/pcapfileformat.cpp @@ -438,6 +438,8 @@ _non_pdml: stream->mutable_core()->set_is_enabled(true); stream->mutable_core()->set_frame_len(pktHdr.inclLen+4); // FCS + stream->mutable_control()->set_num_packets(1); + // setup packet rate to the timing in pcap (as close as possible) const uint kUsecsInSec = uint(1e6); uint usec = (pktHdr.tsSec*kUsecsInSec + pktHdr.tsUsec); diff --git a/common/pdmlprotocols.cpp b/common/pdmlprotocols.cpp index e81351e..ec1f99d 100644 --- a/common/pdmlprotocols.cpp +++ b/common/pdmlprotocols.cpp @@ -192,4 +192,6 @@ void PdmlFrameProtocol::unknownFieldHandler(QString name, int /*pos*/, } } } + else if (name == "frame.number") + stream->mutable_control()->set_num_packets(1); } diff --git a/options.pri b/options.pri index 04ee84a..041da36 100644 --- a/options.pri +++ b/options.pri @@ -1,2 +1,2 @@ -QMAKE_CXXFLAGS += -isystem $$[QT_INSTALL_HEADERS] +QMAKE_CXXFLAGS += -isystem $$[QT_INSTALL_HEADERS] -std=c++11 CONFIG(debug, debug|release): QMAKE_CXXFLAGS_WARN_ON += -Wall -W -Wextra -Werror