diff options
author | Gerald Combs <gerald@wireshark.org> | 2015-01-14 17:25:56 -0800 |
---|---|---|
committer | Gerald Combs <gerald@wireshark.org> | 2015-01-30 06:48:32 +0000 |
commit | 2bf7878e8a7455fe656bb07e9a7d42e6ac4d87fd (patch) | |
tree | 3a0c99831311c43017d1d9b3336856e4a956c353 /ui/qt | |
parent | 6824cee6c4b5f7c00b9dc4e9013aaa936b18b739 (diff) | |
download | wireshark-2bf7878e8a7455fe656bb07e9a7d42e6ac4d87fd.tar.gz |
Qt: Add the RTP Streams dialog.
Add keyboard shortcuts. Note that not all of the buttons made it from
GTK+. Add a "Go to setup frame" option.
Move rtp_streams.c from ui/gtk to ui.
Add a help URL for RTP analysis (which needs to be split into streams +
analysis).
Fix RTP stream packet marking.
Change-Id: Ifb8192ff701a933422509233d76461a46e459f4f
Reviewed-on: https://code.wireshark.org/review/6852
Petri-Dish: Gerald Combs <gerald@wireshark.org>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'ui/qt')
-rw-r--r-- | ui/qt/CMakeLists.txt | 5 | ||||
-rw-r--r-- | ui/qt/Makefile.am | 2 | ||||
-rw-r--r-- | ui/qt/Makefile.common | 4 | ||||
-rw-r--r-- | ui/qt/Wireshark.pro | 3 | ||||
-rw-r--r-- | ui/qt/main_window.h | 1 | ||||
-rw-r--r-- | ui/qt/main_window.ui | 12 | ||||
-rw-r--r-- | ui/qt/main_window_slots.cpp | 13 | ||||
-rw-r--r-- | ui/qt/packet_list.cpp | 1 | ||||
-rw-r--r-- | ui/qt/rtp_stream_dialog.cpp | 636 | ||||
-rw-r--r-- | ui/qt/rtp_stream_dialog.h | 105 | ||||
-rw-r--r-- | ui/qt/rtp_stream_dialog.ui | 246 | ||||
-rw-r--r-- | ui/qt/syntax_line_edit.cpp | 6 | ||||
-rw-r--r-- | ui/qt/traffic_table_dialog.cpp | 3 | ||||
-rw-r--r-- | ui/qt/wireshark_dialog.h | 1 |
14 files changed, 1033 insertions, 5 deletions
diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index 1e20e8d2f1..822d81ad90 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -82,6 +82,7 @@ set(WIRESHARK_QT_HEADERS proto_tree.h qcustomplot.h recent_file_status.h + rtp_stream_dialog.h sctp_all_assocs_dialog.h sctp_assoc_analyse_dialog.h sctp_chunk_statistics_dialog.h @@ -112,7 +113,7 @@ if(HAVE_PCAP_REMOTE) ) endif() -file(GLOB EXTA_QT_HEADERS +file(GLOB EXTRA_QT_HEADERS packet_list_record.h qt_ui_utils.h related_packet_delegate.h @@ -181,6 +182,7 @@ set(WIRESHARK_QT_SRC qt_ui_utils.cpp recent_file_status.cpp related_packet_delegate.cpp + rtp_stream_dialog.cpp sctp_all_assocs_dialog.cpp sctp_assoc_analyse_dialog.cpp sctp_chunk_statistics_dialog.cpp @@ -261,6 +263,7 @@ set(WIRESHARK_QT_UI preferences_dialog.ui print_dialog.ui profile_dialog.ui + rtp_stream_dialog.ui sctp_all_assocs_dialog.ui sctp_assoc_analyse_dialog.ui sctp_chunk_statistics_dialog.ui diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am index d7f4b4f96c..07879dc839 100644 --- a/ui/qt/Makefile.am +++ b/ui/qt/Makefile.am @@ -188,6 +188,8 @@ remote_capture_dialog.cpp remote_capture_dialog.h: ui_remote_capture_dialog.h remote_settings_dialog.cpp remote_settings_dialog.h: ui_remote_settings_dialog.h +rtp_stream_dialog.cpp rtp_stream_dialog.h: ui_rtp_stream_dialog.h + search_frame.cpp search_frame.h: ui_search_frame.h sequence_dialog.cpp sequence_dialog.h: ui_sequence_dialog.h diff --git a/ui/qt/Makefile.common b/ui/qt/Makefile.common index 4c17bb63cb..93a9ca25a6 100644 --- a/ui/qt/Makefile.common +++ b/ui/qt/Makefile.common @@ -63,6 +63,7 @@ NODIST_GENERATED_HEADER_FILES = \ ui_profile_dialog.h \ ui_remote_capture_dialog.h \ ui_remote_settings_dialog.h \ + ui_rtp_stream_dialog.h \ ui_sctp_all_assocs_dialog.h \ ui_sctp_assoc_analyse_dialog.h \ ui_sctp_chunk_statistics_dialog.h \ @@ -177,6 +178,7 @@ MOC_HDRS = \ remote_capture_dialog.h \ remote_settings_dialog.h \ search_frame.h \ + rtp_stream_dialog.h \ sctp_all_assocs_dialog.h \ sctp_assoc_analyse_dialog.h \ sctp_chunk_statistics_dialog.h \ @@ -237,6 +239,7 @@ UI_FILES = \ profile_dialog.ui \ remote_capture_dialog.ui \ remote_settings_dialog.ui \ + rtp_stream_dialog.ui \ sctp_all_assocs_dialog.ui \ sctp_assoc_analyse_dialog.ui \ sctp_chunk_statistics_dialog.ui \ @@ -376,6 +379,7 @@ WIRESHARK_QT_SRC = \ related_packet_delegate.cpp \ remote_capture_dialog.cpp \ remote_settings_dialog.cpp \ + rtp_stream_dialog.cpp \ sctp_all_assocs_dialog.cpp \ sctp_assoc_analyse_dialog.cpp \ sctp_chunk_statistics_dialog.cpp \ diff --git a/ui/qt/Wireshark.pro b/ui/qt/Wireshark.pro index b169212c29..aa49c0481a 100644 --- a/ui/qt/Wireshark.pro +++ b/ui/qt/Wireshark.pro @@ -239,6 +239,7 @@ FORMS += \ profile_dialog.ui \ remote_capture_dialog.ui \ remote_settings_dialog.ui \ + rtp_stream_dialog.ui \ sctp_all_assocs_dialog.ui \ sctp_assoc_analyse_dialog.ui \ sctp_chunk_statistics_dialog.ui \ @@ -292,6 +293,7 @@ HEADERS += $$HEADERS_WS_C \ profile_dialog.h \ remote_capture_dialog.h \ remote_settings_dialog.h \ + rtp_stream_dialog.h \ sctp_all_assocs_dialog.h \ sctp_assoc_analyse_dialog.h \ sctp_chunk_statistics_dialog.h \ @@ -654,6 +656,7 @@ SOURCES += \ related_packet_delegate.cpp \ remote_capture_dialog.cpp \ remote_settings_dialog.cpp \ + rtp_stream_dialog.cpp \ sctp_all_assocs_dialog.cpp \ sctp_assoc_analyse_dialog.cpp \ sctp_chunk_statistics_dialog.cpp \ diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h index d1fedbeaa1..28dd1af301 100644 --- a/ui/qt/main_window.h +++ b/ui/qt/main_window.h @@ -428,6 +428,7 @@ private slots: void openVoipCallsDialog(bool all_flows = false); void on_actionTelephonyVoipCalls_triggered(); void on_actionTelephonyISUPMessages_triggered(); + void on_actionTelephonyRTPStreams_triggered(); void on_actionTelephonyRTSPPacketCounter_triggered(); void on_actionTelephonySMPPOperations_triggered(); void on_actionTelephonyUCPMessages_triggered(); diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui index 0e1888faed..b03092f5df 100644 --- a/ui/qt/main_window.ui +++ b/ui/qt/main_window.ui @@ -442,8 +442,15 @@ </property> <addaction name="actionTelephonyRTSPPacketCounter"/> </widget> + <widget class="QMenu" name="menuRTP"> + <property name="title"> + <string>RTP</string> + </property> + <addaction name="actionTelephonyRTPStreams"/> + </widget> <addaction name="actionTelephonyVoipCalls"/> <addaction name="actionTelephonyISUPMessages"/> + <addaction name="menuRTP"/> <addaction name="menuRTSP"/> <addaction name="actionTelephonySMPPOperations"/> <addaction name="actionTelephonyUCPMessages"/> @@ -2213,6 +2220,11 @@ <string>SIP Flows</string> </property> </action> + <action name="actionTelephonyRTPStreams"> + <property name="text"> + <string>RTP Streams</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <customwidgets> diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp index dad5df9943..1df80ab575 100644 --- a/ui/qt/main_window_slots.cpp +++ b/ui/qt/main_window_slots.cpp @@ -88,6 +88,7 @@ #include "print_dialog.h" #include "profile_dialog.h" #include "qt_ui_utils.h" +#include "rtp_stream_dialog.h" #include "sctp_all_assocs_dialog.h" #include "sctp_assoc_analyse_dialog.h" #include "sctp_graph_dialog.h" @@ -2468,6 +2469,18 @@ void MainWindow::on_actionTelephonyISUPMessages_triggered() openStatisticsTreeDialog("isup_msg"); } +void MainWindow::on_actionTelephonyRTPStreams_triggered() +{ + RtpStreamDialog *rtp_stream_dialog = new RtpStreamDialog(*this, capture_file_); + connect(rtp_stream_dialog, SIGNAL(packetsMarked()), + packet_list_, SLOT(redrawVisiblePackets())); + connect(rtp_stream_dialog, SIGNAL(goToPacket(int)), + packet_list_, SLOT(goToPacket(int))); + connect(rtp_stream_dialog, SIGNAL(updateFilter(QString&, bool)), + this, SLOT(filterPackets(QString&, bool))); + rtp_stream_dialog->show(); +} + void MainWindow::on_actionTelephonyRTSPPacketCounter_triggered() { openStatisticsTreeDialog("rtsp"); diff --git a/ui/qt/packet_list.cpp b/ui/qt/packet_list.cpp index 57e2acc76a..6b465186a1 100644 --- a/ui/qt/packet_list.cpp +++ b/ui/qt/packet_list.cpp @@ -982,7 +982,6 @@ void PacketList::addRelatedFrame(int related_frame) related_packet_delegate_.addRelatedFrame(related_frame); } -#include <QDebug> void PacketList::showHeaderMenu(QPoint pos) { header_ctx_column_ = header()->logicalIndexAt(pos); diff --git a/ui/qt/rtp_stream_dialog.cpp b/ui/qt/rtp_stream_dialog.cpp new file mode 100644 index 0000000000..7a502d5585 --- /dev/null +++ b/ui/qt/rtp_stream_dialog.cpp @@ -0,0 +1,636 @@ +/* rtp_stream_dialog.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "rtp_stream_dialog.h" +#include "ui_rtp_stream_dialog.h" + +#include "file.h" + +#include "epan/addr_resolv.h" +#include <epan/rtp_pt.h> + +#include "ui/utf8_entities.h" + +#include "qt_ui_utils.h" +#include "wireshark_application.h" + +#include <QAction> +#include <QClipboard> +#include <QFileDialog> +#include <QKeyEvent> +#include <QPushButton> +#include <QTextStream> +#include <QTreeWidgetItem> +#include <QTreeWidgetItemIterator> + +#include "tango_colors.h" + +/* + * @file RTP stream dialog + * + * Displays a list of RTP streams with the following information: + * - UDP 4-tuple + * - SSRC + * - Payload type + * - Stats: Packets, lost, max delta, max jitter, mean jitter + * - Problems + * + * Finds reverse streams + * "Save As" rtpdump + * Mark packets + * Go to the setup frame + * Prepare filter + * Copy As CSV and YAML + * Analyze + */ + +// To do: +// - Add more statistics to the hint text (e.g. lost packets). +// - Add more statistics to the main list (e.g. stream duration) + +const int src_addr_col_ = 0; +const int src_port_col_ = 1; +const int dst_addr_col_ = 2; +const int dst_port_col_ = 3; +const int ssrc_col_ = 4; +const int payload_col_ = 5; +const int packets_col_ = 6; +const int lost_col_ = 7; +const int max_delta_col_ = 8; +const int max_jitter_col_ = 9; +const int mean_jitter_col_ = 10; +const int status_col_ = 11; + +Q_DECLARE_METATYPE(rtp_stream_info_t*) + +class RtpStreamTreeWidgetItem : public QTreeWidgetItem +{ +public: + RtpStreamTreeWidgetItem(QTreeWidget *tree, rtp_stream_info_t *stream_info) : QTreeWidgetItem(tree) { + setData(0, Qt::UserRole, qVariantFromValue(stream_info)); + drawData(); + } + + void drawData() { + rtp_stream_info_t *stream_info = data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + if (!stream_info) { + return; + } + setText(src_addr_col_, address_to_display_qstring(&stream_info->src_addr)); + setText(src_port_col_, QString::number(stream_info->src_port)); + setText(dst_addr_col_, address_to_display_qstring(&stream_info->dest_addr)); + setText(dst_port_col_, QString::number(stream_info->dest_port)); + setText(ssrc_col_, QString("0x%1").arg(stream_info->ssrc, 0, 16)); + + if (stream_info->payload_type_name != NULL) { + setText(payload_col_, stream_info->payload_type_name); + } else { + setText(payload_col_, val_to_str_ext(stream_info->payload_type, + &rtp_payload_type_short_vals_ext, + "Unknown (%u)")); + } + + setText(packets_col_, QString::number(stream_info->packet_count)); + + guint32 expected; + double pct_loss; + expected = (stream_info->rtp_stats.stop_seq_nr + stream_info->rtp_stats.cycles*65536) + - stream_info->rtp_stats.start_seq_nr + 1; + lost_ = expected - stream_info->rtp_stats.total_nr; + if (expected) { + pct_loss = (double)(lost_*100.0)/(double)expected; + } else { + pct_loss = 0; + } + + setText(lost_col_, QObject::tr("%1 (%L2%)").arg(lost_).arg(QString::number(pct_loss, 'f', 1))); + setText(max_delta_col_, QString::number(stream_info->rtp_stats.max_delta, 'f', 3)); // This is RTP. Do we need nanoseconds? + setText(max_jitter_col_, QString::number(stream_info->rtp_stats.max_jitter, 'f', 3)); + setText(mean_jitter_col_, QString::number(stream_info->rtp_stats.mean_jitter, 'f', 3)); + + if (stream_info->problem) { + setText(status_col_, UTF8_BULLET); + setTextAlignment(status_col_, Qt::AlignCenter); + for (int i = 0; i < columnCount(); i++) { + setBackgroundColor(i, ws_css_warn_background); + setTextColor(i, ws_css_warn_text); + } + } + } + // Return a QString, int, double, or invalid QVariant representing the raw column data. + QVariant colData(int col) const { + rtp_stream_info_t *stream_info = data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + if (!stream_info) { + return QVariant(); + } + + switch(col) { + case src_addr_col_: + case dst_addr_col_: + case payload_col_: // XXX Return numeric value? + return text(col); + case src_port_col_: + return stream_info->src_port; + case dst_port_col_: + return stream_info->dest_port; + case ssrc_col_: + return stream_info->ssrc; + case packets_col_: + return stream_info->packet_count; + case lost_col_: + return lost_; + case max_delta_col_: + return stream_info->rtp_stats.max_delta; + case max_jitter_col_: + return stream_info->rtp_stats.max_jitter; + case mean_jitter_col_: + return stream_info->rtp_stats.mean_jitter; + case status_col_: + return stream_info->problem ? "Problem" : ""; + default: + break; + } + return QVariant(); + } + + bool operator< (const QTreeWidgetItem &other) const + { + rtp_stream_info_t *this_stream_info = data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + rtp_stream_info_t *other_stream_info = other.data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + if (!this_stream_info || !other_stream_info) { + return false; + } + const RtpStreamTreeWidgetItem &other_rstwi = dynamic_cast<const RtpStreamTreeWidgetItem&>(other); + + switch (treeWidget()->sortColumn()) { + case src_addr_col_: + return cmp_address(&(this_stream_info->src_addr), &(other_stream_info->src_addr)) < 0; + case src_port_col_: + return this_stream_info->src_port < other_stream_info->src_port; + case dst_addr_col_: + return cmp_address(&(this_stream_info->dest_addr), &(other_stream_info->dest_addr)) < 0; + case dst_port_col_: + return this_stream_info->dest_port < other_stream_info->dest_port; + case ssrc_col_: + return this_stream_info->ssrc < other_stream_info->ssrc; + case payload_col_: + return this_stream_info->payload_type < other_stream_info->payload_type; // XXX Compare payload_type_name instead? + case packets_col_: + return this_stream_info->packet_count < other_stream_info->packet_count; + case lost_col_: + return lost_ < other_rstwi.lost_; + case max_delta_col_: + return this_stream_info->rtp_stats.max_delta < other_stream_info->rtp_stats.max_delta; + case max_jitter_col_: + return this_stream_info->rtp_stats.max_jitter < other_stream_info->rtp_stats.max_jitter; + case mean_jitter_col_: + return this_stream_info->rtp_stats.mean_jitter < other_stream_info->rtp_stats.mean_jitter; + default: + break; + } + + // Fall back to string comparison + return QTreeWidgetItem::operator <(other); + } + +private: + guint32 lost_; +}; + +RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) : + WiresharkDialog(parent, cf), + ui(new Ui::RtpStreamDialog), + need_redraw_(false) +{ + ui->setupUi(this); + setWindowSubtitle(tr("RTP Streams")); + ui->streamTreeWidget->installEventFilter(this); + + // XXX Use recent settings instead + resize(parent.width() * 4 / 5, parent.height() * 2 / 3); + + ctx_menu_.addAction(ui->actionSelectNone); + ctx_menu_.addAction(ui->actionFindReverse); + ctx_menu_.addAction(ui->actionGoToSetup); + ctx_menu_.addAction(ui->actionMarkPackets); + ctx_menu_.addAction(ui->actionPrepareFilter); + ctx_menu_.addAction(ui->actionExportAsRtpDump); + ctx_menu_.addAction(ui->actionCopyAsCsv); + ctx_menu_.addAction(ui->actionCopyAsYaml); + ctx_menu_.addAction(ui->actionAnalyze); + ui->streamTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), + SLOT(showStreamMenu(QPoint))); + + // Some GTK+ buttons have been left out intentionally in order to + // reduce clutter. Do you have a strong and informed opinion about + // this? Perhaps you should volunteer to maintain this code! + find_reverse_button_ = ui->buttonBox->addButton(ui->actionFindReverse->text(), QDialogButtonBox::ApplyRole); + find_reverse_button_->setToolTip(ui->actionFindReverse->toolTip()); + prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ApplyRole); + prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip()); + export_button_ = ui->buttonBox->addButton(tr("Export..."), QDialogButtonBox::ApplyRole); + export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip()); + copy_button_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ApplyRole); + analyze_button_ = ui->buttonBox->addButton(ui->actionAnalyze->text(), QDialogButtonBox::ApplyRole); + analyze_button_->setToolTip(ui->actionAnalyze->toolTip()); + + QMenu *copy_menu = new QMenu(); + QAction *ca; + ca = copy_menu->addAction(tr("as CSV")); + ca->setToolTip(ui->actionCopyAsCsv->toolTip()); + connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsCsv_triggered())); + ca = copy_menu->addAction(tr("as YAML")); + ca->setToolTip(ui->actionCopyAsYaml->toolTip()); + connect(ca, SIGNAL(triggered()), this, SLOT(on_actionCopyAsYaml_triggered())); + copy_button_->setMenu(copy_menu); + + /* Register the tap listener */ + memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t)); + tapinfo_.tap_draw = tapDraw; + tapinfo_.tap_mark_packet = tapMarkPacket; + tapinfo_.tap_data = this; + tapinfo_.mode = TAP_ANALYSE; + + register_tap_listener_rtp_stream(&tapinfo_); + /* Scan for RTP streams (redissect all packets) */ + rtpstream_scan(&tapinfo_, cf.capFile()); + + updateWidgets(); +} + +RtpStreamDialog::~RtpStreamDialog() +{ + delete ui; + remove_tap_listener_rtp_stream(&tapinfo_); +} + +bool RtpStreamDialog::eventFilter(QObject *obj, QEvent *event) +{ + Q_UNUSED(obj) + if (ui->streamTreeWidget->hasFocus() && event->type() == QEvent::KeyPress) { + QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event); + switch(keyEvent.key()) { + case Qt::Key_G: + on_actionGoToSetup_triggered(); + return true; + case Qt::Key_M: + on_actionMarkPackets_triggered(); + return true; + case Qt::Key_P: + on_actionPrepareFilter_triggered(); + return true; + case Qt::Key_R: + on_actionFindReverse_triggered(); + return true; + case Qt::Key_A: + // XXX "Shift+Ctrl+A" is a fairly standard shortcut for "select none". + // However, the main window uses this for displaying the profile dialog. +// if (keyEvent.modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) +// on_actionSelectNone_triggered(); +// return true; + break; + default: + break; + } + } + return false; +} + +void RtpStreamDialog::tapDraw(void *tapinfo_ptr) +{ + rtpstream_tapinfo_t *tapinfo = (rtpstream_tapinfo_t *) tapinfo_ptr; + + RtpStreamDialog *rtp_stream_dialog = static_cast<RtpStreamDialog *>(tapinfo->tap_data); + if (rtp_stream_dialog) { + rtp_stream_dialog->updateStreams(); + } +} + +void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd) +{ + if (!tapinfo) return; + + RtpStreamDialog *rtp_stream_dialog = static_cast<RtpStreamDialog *>(tapinfo->tap_data); + if (rtp_stream_dialog) { + rtp_stream_dialog->need_redraw_ = true; + cf_mark_frame(rtp_stream_dialog->cap_file_.capFile(), fd); + rtp_stream_dialog->need_redraw_ = true; + } +} + +void RtpStreamDialog::updateStreams() +{ + GList *cur_stream = g_list_nth(tapinfo_.strinfo_list, ui->streamTreeWidget->topLevelItemCount()); + + // Add any missing items + while (cur_stream && cur_stream->data) { + rtp_stream_info_t *stream_info = (rtp_stream_info_t*) cur_stream->data; + new RtpStreamTreeWidgetItem(ui->streamTreeWidget, stream_info); + cur_stream = g_list_next(cur_stream); + } + + // Recalculate values + QTreeWidgetItemIterator iter(ui->streamTreeWidget); + while (*iter) { + RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter); + rsti->drawData(); + ++iter; + } + + // Resize columns + for (int i = 0; i < ui->streamTreeWidget->columnCount(); i++) { + ui->streamTreeWidget->resizeColumnToContents(i); + } + + ui->streamTreeWidget->setSortingEnabled(true); + + updateWidgets(); + + if (need_redraw_) { + emit packetsMarked(); + need_redraw_ = false; + } +} + +void RtpStreamDialog::updateWidgets() +{ + bool selected = ui->streamTreeWidget->selectedItems().count() > 0; + + QString hint = "<small><i>"; + hint += tr("%1 streams").arg(ui->streamTreeWidget->topLevelItemCount()); + + if (selected) { + int tot_packets = 0; + foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) { + rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>(); + if (stream_info) { + tot_packets += stream_info->packet_count; + } + } + hint += tr(", %1 selected, %2 total packets") + .arg(ui->streamTreeWidget->selectedItems().count()) + .arg(tot_packets); + } + + hint += ". Right-click for more options."; + hint += "</i></small>"; + ui->hintLabel->setText(hint); + + bool enable = selected && !file_closed_; + bool has_data = ui->streamTreeWidget->topLevelItemCount() > 0; + + find_reverse_button_->setEnabled(enable); + prepare_button_->setEnabled(enable); + export_button_->setEnabled(enable); + copy_button_->setEnabled(has_data); + analyze_button_->setEnabled(false); // XXX No dialog + + ui->actionFindReverse->setEnabled(enable); + ui->actionGoToSetup->setEnabled(enable); + ui->actionMarkPackets->setEnabled(enable); + ui->actionPrepareFilter->setEnabled(enable); + ui->actionExportAsRtpDump->setEnabled(enable); + ui->actionCopyAsCsv->setEnabled(has_data); + ui->actionCopyAsYaml->setEnabled(has_data); + ui->actionAnalyze->setEnabled(false); // XXX No dialog +} + +QList<QVariant> RtpStreamDialog::streamRowData(int row) const +{ + QList<QVariant> row_data; + + if (row >= ui->streamTreeWidget->topLevelItemCount()) { + return row_data; + } + + for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) { + if (row < 0) { + row_data << ui->streamTreeWidget->headerItem()->text(col); + } else { + RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(row)); + if (rsti) { + row_data << rsti->colData(col); + } + } + } + return row_data; +} + +void RtpStreamDialog::captureFileClosing() +{ + remove_tap_listener_rtp_stream(&tapinfo_); + WiresharkDialog::captureFileClosing(); +} + +void RtpStreamDialog::showStreamMenu(QPoint pos) +{ + ctx_menu_.popup(ui->streamTreeWidget->viewport()->mapToGlobal(pos)); +} + +void RtpStreamDialog::on_actionAnalyze_triggered() +{ + +} + +void RtpStreamDialog::on_actionCopyAsCsv_triggered() +{ + QString csv; + QTextStream stream(&csv, QIODevice::Text); + for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row++) { + QStringList rdsl; + foreach (QVariant v, streamRowData(row)) { + if (!v.isValid()) { + rdsl << "\"\""; + } else if ((int) v.type() == (int) QMetaType::QString) { + rdsl << QString("\"%1\"").arg(v.toString()); + } else { + rdsl << v.toString(); + } + } + stream << rdsl.join(",") << endl; + } + wsApp->clipboard()->setText(stream.readAll()); +} + +void RtpStreamDialog::on_actionCopyAsYaml_triggered() +{ + QString yaml; + QTextStream stream(&yaml, QIODevice::Text); + stream << "---" << endl; + for (int row = -1; row < ui->streamTreeWidget->topLevelItemCount(); row ++) { + stream << "-" << endl; + foreach (QVariant v, streamRowData(row)) { + stream << " - " << v.toString() << endl; + } + } + wsApp->clipboard()->setText(stream.readAll()); +} + +void RtpStreamDialog::on_actionExportAsRtpDump_triggered() +{ + if (file_closed_ || ui->streamTreeWidget->selectedItems().count() < 1) return; + + // XXX If the user selected multiple frames is this the one we actually want? + QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0]; + rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>(); + if (stream_info) { + QString file_name; + QDir path(wsApp->lastOpenDir()); + QString save_file = path.canonicalPath() + "/" + cap_file_.fileTitle(); + QString extension; + file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save RTPDump As" UTF8_HORIZONTAL_ELLIPSIS)), + save_file, "RTPDump Format (*.rtpdump)", &extension); + + if (file_name.length() > 0) { + gchar *dest_file = qstring_strdup(file_name); + gboolean save_ok = rtpstream_save(&tapinfo_, cap_file_.capFile(), stream_info, dest_file); + g_free(dest_file); + // else error dialog? + if (save_ok) { + path = QDir(file_name); + wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData()); + } + } + + } +} + +void RtpStreamDialog::on_actionFindReverse_triggered() +{ + if (ui->streamTreeWidget->selectedItems().count() < 1) return; + + // Gather up our selected streams... + QList<rtp_stream_info_t *> selected_streams; + foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) { + rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>(); + if (stream_info) { + selected_streams << stream_info; + } + } + + // ...and compare them to our unselected streams. + QTreeWidgetItemIterator iter(ui->streamTreeWidget, QTreeWidgetItemIterator::Unselected); + while (*iter) { + rtp_stream_info_t *stream = (*iter)->data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + if (stream) { + foreach (rtp_stream_info_t *fwd_stream, selected_streams) { + if (rtp_stream_info_is_reverse(fwd_stream, stream)) { + (*iter)->setSelected(true); + } + } + } + ++iter; + } +} + +void RtpStreamDialog::on_actionGoToSetup_triggered() +{ + if (ui->streamTreeWidget->selectedItems().count() < 1) return; + // XXX If the user selected multiple frames is this the one we actually want? + QTreeWidgetItem *ti = ui->streamTreeWidget->selectedItems()[0]; + rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>(); + if (stream_info) { + emit goToPacket(stream_info->setup_frame_number); + } +} + +void RtpStreamDialog::on_actionMarkPackets_triggered() +{ + if (ui->streamTreeWidget->selectedItems().count() < 1) return; + rtp_stream_info_t *stream_a, *stream_b = NULL; + + stream_a = ui->streamTreeWidget->selectedItems()[0]->data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + if (ui->streamTreeWidget->selectedItems().count() > 1) + stream_b = ui->streamTreeWidget->selectedItems()[1]->data(0, Qt::UserRole).value<rtp_stream_info_t*>(); + + if (stream_a == NULL && stream_b == NULL) return; + + // XXX Mark the setup frame as well? + need_redraw_ = false; + rtpstream_mark(&tapinfo_, cap_file_.capFile(), stream_a, stream_b); + updateWidgets(); +} + +void RtpStreamDialog::on_actionPrepareFilter_triggered() +{ + if (ui->streamTreeWidget->selectedItems().count() < 1) return; + + // Gather up our selected streams... + QStringList stream_filters; + foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) { + rtp_stream_info_t *stream_info = ti->data(0, Qt::UserRole).value<rtp_stream_info_t *>(); + if (stream_info) { + QString ip_proto = stream_info->src_addr.type == AT_IPv6 ? "ipv6" : "ip"; + stream_filters << QString("(%1.src==%2 && udp.srcport==%3 && %1.dst==%4 && udp.dstport==%5 && rtp.ssrc==0x%6)") + .arg(ip_proto) // %1 + .arg(address_to_qstring(&stream_info->src_addr)) // %2 + .arg(stream_info->src_port) // %3 + .arg(address_to_qstring(&stream_info->dest_addr)) // %4 + .arg(stream_info->dest_port) // %5 + .arg(stream_info->ssrc, 0, 16); + } + } + if (stream_filters.length() > 0) { + QString filter = stream_filters.join(" || "); + remove_tap_listener_rtp_stream(&tapinfo_); + emit updateFilter(filter); + } +} + +void RtpStreamDialog::on_actionSelectNone_triggered() +{ + ui->streamTreeWidget->clearSelection(); +} + +void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged() +{ + updateWidgets(); +} + +void RtpStreamDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + if (button == prepare_button_) { + on_actionPrepareFilter_triggered(); + } else if (button == export_button_) { + on_actionExportAsRtpDump_triggered(); + } else if (button == analyze_button_) { + + } +} + +void RtpStreamDialog::on_buttonBox_helpRequested() +{ + wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/rtp_stream_dialog.h b/ui/qt/rtp_stream_dialog.h new file mode 100644 index 0000000000..1615d5453e --- /dev/null +++ b/ui/qt/rtp_stream_dialog.h @@ -0,0 +1,105 @@ +/* rtp_stream_dialog.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RTP_STREAM_DIALOG_H +#define RTP_STREAM_DIALOG_H + +#include "wireshark_dialog.h" + +#include "ui/rtp_stream.h" + +#include <QAbstractButton> +#include <QMenu> + +namespace Ui { +class RtpStreamDialog; +} + +class RtpStreamDialog : public WiresharkDialog +{ + Q_OBJECT + +public: + explicit RtpStreamDialog(QWidget &parent, CaptureFile &cf); + ~RtpStreamDialog(); + +signals: + // Tells the packet list to redraw. An alternative might be to add a + // cf_packet_marked callback to file.[ch] but that's synchronous and + // might incur too much overhead. + void packetsMarked(); + void updateFilter(QString &filter, bool force = false); + void goToPacket(int packet_num); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private: + Ui::RtpStreamDialog *ui; + rtpstream_tapinfo_t tapinfo_; + QPushButton *find_reverse_button_; + QPushButton *prepare_button_; + QPushButton *export_button_; + QPushButton *copy_button_; + QPushButton *analyze_button_; + QMenu ctx_menu_; + bool need_redraw_; + + static void tapDraw(void *tapinfo_ptr); + static void tapMarkPacket(rtpstream_tapinfo_t *tapinfo, frame_data *fd); + + void updateStreams(); + void updateWidgets(); + + QList<QVariant> streamRowData(int row) const; + + +private slots: + void captureFileClosing(); + void showStreamMenu(QPoint pos); + void on_actionCopyAsCsv_triggered(); + void on_actionCopyAsYaml_triggered(); + void on_actionFindReverse_triggered(); + void on_actionGoToSetup_triggered(); + void on_actionMarkPackets_triggered(); + void on_actionPrepareFilter_triggered(); + void on_actionSelectNone_triggered(); + void on_streamTreeWidget_itemSelectionChanged(); + void on_buttonBox_helpRequested(); + void on_buttonBox_clicked(QAbstractButton *button); + void on_actionExportAsRtpDump_triggered(); + void on_actionAnalyze_triggered(); +}; + +#endif // RTP_STREAM_DIALOG_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/rtp_stream_dialog.ui b/ui/qt/rtp_stream_dialog.ui new file mode 100644 index 0000000000..03573aa0a0 --- /dev/null +++ b/ui/qt/rtp_stream_dialog.ui @@ -0,0 +1,246 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RtpStreamDialog</class> + <widget class="QDialog" name="RtpStreamDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>460</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeWidget" name="streamTreeWidget"> + <property name="selectionMode"> + <enum>QAbstractItemView::MultiSelection</enum> + </property> + <property name="textElideMode"> + <enum>Qt::ElideMiddle</enum> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerDefaultSectionSize"> + <number>50</number> + </attribute> + <column> + <property name="text"> + <string>Source Address</string> + </property> + </column> + <column> + <property name="text"> + <string>Source Port</string> + </property> + </column> + <column> + <property name="text"> + <string>Destination Address</string> + </property> + </column> + <column> + <property name="text"> + <string>Destination Port</string> + </property> + </column> + <column> + <property name="text"> + <string>SSRC</string> + </property> + </column> + <column> + <property name="text"> + <string>Payload</string> + </property> + </column> + <column> + <property name="text"> + <string>Packets</string> + </property> + </column> + <column> + <property name="text"> + <string>Lost</string> + </property> + </column> + <column> + <property name="text"> + <string>Max Delta (ms)</string> + </property> + </column> + <column> + <property name="text"> + <string>Max Jitter</string> + </property> + </column> + <column> + <property name="text"> + <string>Mean Jitter</string> + </property> + </column> + <column> + <property name="text"> + <string>Status</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QLabel" name="hintLabel"> + <property name="text"> + <string><small><i>A hint.</i></small></string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close|QDialogButtonBox::Help</set> + </property> + </widget> + </item> + </layout> + <action name="actionFindReverse"> + <property name="text"> + <string>Find Reverse</string> + </property> + <property name="toolTip"> + <string>Find the reverse stream matching the selected forward stream.</string> + </property> + <property name="shortcut"> + <string>R</string> + </property> + </action> + <action name="actionMarkPackets"> + <property name="text"> + <string>Mark Packets</string> + </property> + <property name="toolTip"> + <string>Mark the packets of the selected stream(s).</string> + </property> + <property name="shortcut"> + <string>M</string> + </property> + </action> + <action name="actionSelectNone"> + <property name="text"> + <string>Select None</string> + </property> + <property name="toolTip"> + <string>Undo stream selection.</string> + </property> + </action> + <action name="actionGoToSetup"> + <property name="text"> + <string>Go To Setup</string> + </property> + <property name="toolTip"> + <string>Go to the setup packet for this stream.</string> + </property> + <property name="shortcut"> + <string>G</string> + </property> + </action> + <action name="actionPrepareFilter"> + <property name="text"> + <string>Prepare Filter</string> + </property> + <property name="toolTip"> + <string>Prepare a filter matching the selected stream(s).</string> + </property> + <property name="shortcut"> + <string>P</string> + </property> + </action> + <action name="actionExportAsRtpDump"> + <property name="text"> + <string>Export As RTPDump</string> + </property> + <property name="toolTip"> + <string>Export the stream payload as rtpdump</string> + </property> + <property name="shortcut"> + <string>E</string> + </property> + </action> + <action name="actionAnalyze"> + <property name="text"> + <string>Analyze</string> + </property> + <property name="toolTip"> + <string>Open the analysis window for the selected stream(s)</string> + </property> + </action> + <action name="actionCopyAsCsv"> + <property name="text"> + <string>Copy as CSV</string> + </property> + <property name="toolTip"> + <string>Copy stream list as CSV.</string> + </property> + </action> + <action name="actionCopyAsYaml"> + <property name="text"> + <string>Copy as YAML</string> + </property> + <property name="toolTip"> + <string>Copy stream list as YAML.</string> + </property> + </action> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>RtpStreamDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>RtpStreamDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/ui/qt/syntax_line_edit.cpp b/ui/qt/syntax_line_edit.cpp index 483bdcb7ce..867c01d38a 100644 --- a/ui/qt/syntax_line_edit.cpp +++ b/ui/qt/syntax_line_edit.cpp @@ -59,9 +59,9 @@ void SyntaxLineEdit::setSyntaxState(SyntaxState state) { .arg(Invalid) .arg(Deprecated) .arg("palette(text)") // Foreground - .arg(ColorUtils::fromColorT(&prefs.gui_text_valid).name()) // Invalid - .arg(ColorUtils::fromColorT(&prefs.gui_text_invalid).name()) // Deprecated - .arg(ColorUtils::fromColorT(&prefs.gui_text_deprecated).name()) // Valid + .arg(ColorUtils::fromColorT(&prefs.gui_text_valid).name()) // Valid + .arg(ColorUtils::fromColorT(&prefs.gui_text_invalid).name()) // Invalid + .arg(ColorUtils::fromColorT(&prefs.gui_text_deprecated).name()) // VDeprecated ; setStyleSheet(style_sheet_); } diff --git a/ui/qt/traffic_table_dialog.cpp b/ui/qt/traffic_table_dialog.cpp index e4b1034c64..4e3865f215 100644 --- a/ui/qt/traffic_table_dialog.cpp +++ b/ui/qt/traffic_table_dialog.cpp @@ -44,6 +44,9 @@ #include <QTextStream> #include <QToolButton> +// To do: +// - Add "copy" items to the menu. + // Bugs: // - Name resolution doesn't do anything if its preference is disabled. // - Columns don't resize correctly. diff --git a/ui/qt/wireshark_dialog.h b/ui/qt/wireshark_dialog.h index 95be7f05c6..36072a79ea 100644 --- a/ui/qt/wireshark_dialog.h +++ b/ui/qt/wireshark_dialog.h @@ -39,6 +39,7 @@ signals: public slots: protected: + virtual void keyPressEvent(QKeyEvent *event) { QDialog::keyPressEvent(event); } void setWindowSubtitle(const QString &subtitle); virtual void updateWidgets(); |