aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorSimon Barber <simon.barber@meraki.net>2017-04-06 17:45:33 -0400
committerMichael Mann <mmann78@netscape.net>2017-06-05 11:25:51 +0000
commit21305e9835d40dbd812514471397fb1de3a0740c (patch)
treefdbd995627e1e7cf059a9bdc3b1d2b8dc7278beb /ui
parentd0865fd619454a9ac06b1c7d287dc438aff50bb0 (diff)
Add a timeline view for packets, with the timing data used to generate the display
taken from the timing analysis done in the wlan_radio dissector. QT only. The timeline background is light gray, white for packets displayed in the packetlist, and blue for the currently selected packet. Packets are coloured according to the colouring rules foreground colour. The timeline can be zoomed with controls on the toolbar. At higher zoom levels the duration (NAV) field is plotted as a horizontal line to the right of a packet. The height of a packet in the timeline is proportional to the RSSI. The bottom half of the packet is only shown if it matches the display filter. Todo: Auto detect TSF timing reference point (start/end of packet) Add a scrollbar Add a ruler showing time Improve handling of focus. Do not display NAV for packets with bad FCS. Show related packets graphically Different Y axis modes - bandwidth/channel use display - different transmitters per line - background color from coloring rules Live capture support Change-Id: Ic31fffb0d6854966361ade7abb5c0be50db9a247 Reviewed-on: https://code.wireshark.org/review/20043 Petri-Dish: Michael Mann <mmann78@netscape.net> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Michael Mann <mmann78@netscape.net>
Diffstat (limited to 'ui')
-rw-r--r--ui/qt/CMakeLists.txt2
-rw-r--r--ui/qt/Makefile.am2
-rw-r--r--ui/qt/main_window.cpp12
-rw-r--r--ui/qt/main_window.h5
-rw-r--r--ui/qt/main_window.ui37
-rw-r--r--ui/qt/main_window_slots.cpp20
-rw-r--r--ui/qt/packet_list_model.cpp4
-rw-r--r--ui/qt/packet_list_model.h2
-rw-r--r--ui/qt/wireless_timeline.cpp643
-rw-r--r--ui/qt/wireless_timeline.h122
10 files changed, 849 insertions, 0 deletions
diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt
index ca3d67f530..9addc44d43 100644
--- a/ui/qt/CMakeLists.txt
+++ b/ui/qt/CMakeLists.txt
@@ -170,6 +170,7 @@ set(WIRESHARK_QT_HEADERS
voip_calls_dialog.h
voip_calls_info_model.h
wireless_frame.h
+ wireless_timeline.h
wireshark_application.h
wireshark_dialog.h
wlan_statistics_dialog.h
@@ -344,6 +345,7 @@ set(WIRESHARK_QT_SRC
voip_calls_dialog.cpp
voip_calls_info_model.cpp
wireless_frame.cpp
+ wireless_timeline.cpp
wireshark_application.cpp
wireshark_dialog.cpp
${WIRESHARK_CUSTOM_QT_SRCS}
diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am
index 7f52918fcf..ee9c82a6f1 100644
--- a/ui/qt/Makefile.am
+++ b/ui/qt/Makefile.am
@@ -302,6 +302,7 @@ MOC_HDRS = \
voip_calls_dialog.h \
voip_calls_info_model.h \
wireless_frame.h \
+ wireless_timeline.h \
wireshark_application.h \
wireshark_dialog.h \
wlan_statistics_dialog.h
@@ -589,6 +590,7 @@ WIRESHARK_QT_SRC = \
voip_calls_dialog.cpp \
voip_calls_info_model.cpp \
wireless_frame.cpp \
+ wireless_timeline.cpp \
wireshark_application.cpp \
wireshark_dialog.cpp
diff --git a/ui/qt/main_window.cpp b/ui/qt/main_window.cpp
index d9036cb3e9..543dd1c9da 100644
--- a/ui/qt/main_window.cpp
+++ b/ui/qt/main_window.cpp
@@ -71,6 +71,7 @@ DIAG_ON(frame-larger-than=)
#include "import_text_dialog.h"
#include "interface_toolbar.h"
#include "packet_list.h"
+#include "wireless_timeline.h"
#include "proto_tree.h"
#include "simple_dialog.h"
#include "stock_icon.h"
@@ -338,6 +339,7 @@ MainWindow::MainWindow(QWidget *parent) :
cur_layout_(QVector<unsigned>()),
df_combo_box_(NULL),
packet_list_(NULL),
+ wireless_timeline_(NULL),
proto_tree_(NULL),
previous_focus_(NULL),
file_set_dialog_(NULL),
@@ -526,6 +528,7 @@ MainWindow::MainWindow(QWidget *parent) :
empty_pane_.setObjectName("emptyPane");
packet_list_ = new PacketList(&master_split_);
+ wireless_timeline_ = new WirelessTimeline(&master_split_, packet_list_);
proto_tree_ = new ProtoTree(&master_split_);
proto_tree_->installEventFilter(this);
@@ -1985,6 +1988,10 @@ void MainWindow::initMainToolbarIcons()
main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out"));
main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original"));
main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns"));
+
+ main_ui_->actionWirelessTimelineZoomIn->setIcon(StockIcon("zoom-in"));
+ main_ui_->actionWirelessTimelineZoomOut->setIcon(StockIcon("zoom-out"));
+ main_ui_->actionWirelessTimelineZoomFullOut->setIcon(StockIcon("zoom-original"));
}
void MainWindow::initShowHideMainWidgets()
@@ -2415,6 +2422,11 @@ void MainWindow::setForCapturedPackets(bool have_captured_packets)
main_ui_->actionViewNormalSize->setEnabled(have_captured_packets);
main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets);
+ bool wireless_timeline_visible = (wireless_timeline_ ? !wireless_timeline_->isHidden() : FALSE);
+ main_ui_->actionWirelessTimelineZoomIn->setEnabled(wireless_timeline_visible);
+ main_ui_->actionWirelessTimelineZoomOut->setEnabled(wireless_timeline_visible);
+ main_ui_->actionWirelessTimelineZoomFullOut->setEnabled(wireless_timeline_visible);
+
main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets);
main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets);
main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h
index b6c41ea1c1..18df63840f 100644
--- a/ui/qt/main_window.h
+++ b/ui/qt/main_window.h
@@ -66,6 +66,7 @@ class FileSetDialog;
class FunnelStatistics;
class MainWelcome;
class PacketList;
+class WirelessTimeline;
class ProtoTree;
class WirelessFrame;
@@ -152,6 +153,7 @@ private:
// XXX - packet_list_, proto_tree_, and byte_view_tab_ should
// probably be full-on values instead of pointers.
PacketList *packet_list_;
+ WirelessTimeline *wireless_timeline_;
ProtoTree *proto_tree_;
QWidget *previous_focus_;
FileSetDialog *file_set_dialog_;
@@ -456,6 +458,9 @@ private slots:
void on_actionViewZoomIn_triggered();
void on_actionViewZoomOut_triggered();
void on_actionViewNormalSize_triggered();
+ void on_actionWirelessTimelineZoomIn_triggered();
+ void on_actionWirelessTimelineZoomOut_triggered();
+ void on_actionWirelessTimelineZoomFullOut_triggered();
void on_actionViewColorizePacketList_triggered(bool checked);
void on_actionViewColoringRules_triggered();
void colorizeConversation(bool create_rule = false);
diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui
index e38896a5e9..7c359249c5 100644
--- a/ui/qt/main_window.ui
+++ b/ui/qt/main_window.ui
@@ -738,6 +738,10 @@
<addaction name="actionViewZoomOut"/>
<addaction name="actionViewNormalSize"/>
<addaction name="actionViewResizeColumns"/>
+ <addaction name="separator"/>
+ <addaction name="actionWirelessTimelineZoomIn"/>
+ <addaction name="actionWirelessTimelineZoomOut"/>
+ <addaction name="actionWirelessTimelineZoomFullOut"/>
</widget>
<widget class="MainStatusBar" name="statusBar"/>
<widget class="QToolBar" name="displayFilterToolBar">
@@ -2982,6 +2986,39 @@
<string>&amp;Full Screen</string>
</property>
</action>
+ <action name="actionWirelessTimelineZoomIn">
+ <property name="text">
+ <string>&amp;Zoom In</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom in on the wireless timeline</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+I</string>
+ </property>
+ </action>
+ <action name="actionWirelessTimelineZoomOut">
+ <property name="text">
+ <string>Zoom Out</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom out on the wireless timeline</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionWirelessTimelineZoomFullOut">
+ <property name="text">
+ <string>Full capture</string>
+ </property>
+ <property name="toolTip">
+ <string>Fully zoom out on the wireless timeline</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+U</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 82f9cc7543..aafee8bcca 100644
--- a/ui/qt/main_window_slots.cpp
+++ b/ui/qt/main_window_slots.cpp
@@ -165,6 +165,7 @@ DIAG_ON(frame-larger-than=)
#include "voip_calls_dialog.h"
#include "wireshark_application.h"
#include "wlan_statistics_dialog.h"
+#include "wireless_timeline.h"
#include <QClipboard>
#include <QFileInfo>
@@ -786,6 +787,7 @@ void MainWindow::captureFileReadStarted(const QString &action) {
main_ui_->statusBar->pushFileStatus(msg, msgtip);
main_ui_->mainStack->setCurrentWidget(&master_split_);
main_ui_->actionAnalyzeReloadLuaPlugins->setEnabled(false);
+ wireless_timeline_->captureFileReadStarted(capture_file_.capFile());
WiresharkApplication::processEvents();
}
@@ -806,6 +808,9 @@ void MainWindow::captureFileReadFinished() {
/* Update the appropriate parts of the main window. */
updateForUnsavedChanges();
+ /* enable wireless timeline if capture allows it */
+ wireless_timeline_->captureFileReadFinished();
+
/* Enable menu items that make sense if you have some captured packets. */
setForCapturedPackets(true);
@@ -2462,6 +2467,21 @@ void MainWindow::on_actionViewNormalSize_triggered()
zoomText();
}
+void MainWindow::on_actionWirelessTimelineZoomIn_triggered()
+{
+ wireless_timeline_->zoomIn();
+}
+
+void MainWindow::on_actionWirelessTimelineZoomOut_triggered()
+{
+ wireless_timeline_->zoomOut();
+}
+
+void MainWindow::on_actionWirelessTimelineZoomFullOut_triggered()
+{
+ wireless_timeline_->zoomFullOut();
+}
+
void MainWindow::on_actionViewColorizePacketList_triggered(bool checked) {
recent.packet_list_colorize = checked;
packet_list_enable_color(checked);
diff --git a/ui/qt/packet_list_model.cpp b/ui/qt/packet_list_model.cpp
index 4c8012ea91..a46b6f568d 100644
--- a/ui/qt/packet_list_model.cpp
+++ b/ui/qt/packet_list_model.cpp
@@ -615,6 +615,7 @@ void PacketListModel::dissectIdle(bool reset)
idle_dissection_timer_->restart();
+ int first = idle_dissection_row_;
while (idle_dissection_timer_->elapsed() < idle_dissection_interval_
&& idle_dissection_row_ < physical_rows_.count()) {
ensureRowColorized(idle_dissection_row_);
@@ -627,6 +628,9 @@ void PacketListModel::dissectIdle(bool reset)
} else {
idle_dissection_timer_->invalidate();
}
+
+ // report colorization progress
+ bgColorizationProgress(first+1, idle_dissection_row_+1);
}
// XXX Pass in cinfo from packet_list_append so that we can fill in
diff --git a/ui/qt/packet_list_model.h b/ui/qt/packet_list_model.h
index 40360f5552..3af6c60042 100644
--- a/ui/qt/packet_list_model.h
+++ b/ui/qt/packet_list_model.h
@@ -87,6 +87,8 @@ signals:
void updateProgressStatus(int value);
void popProgressStatus();
+ void bgColorizationProgress(int first, int last);
+
public slots:
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
void flushVisibleRows();
diff --git a/ui/qt/wireless_timeline.cpp b/ui/qt/wireless_timeline.cpp
new file mode 100644
index 0000000000..7fb583528c
--- /dev/null
+++ b/ui/qt/wireless_timeline.cpp
@@ -0,0 +1,643 @@
+/* wireless_timeline.cpp
+ * GUI to show an 802.11 wireless timeline of packets
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * Copyright 2012 Parc Inc and Samsung Electronics
+ * Copyright 2015, 2016 & 2017 Cisco Inc
+ *
+ * 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 "wireless_timeline.h"
+
+#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <epan/proto_data.h>
+#include <epan/packet_info.h>
+#include <epan/column-utils.h>
+#include <epan/tap.h>
+
+#include <cmath>
+
+#include "globals.h"
+#include "color_utils.h"
+#include "../../log.h"
+#include <epan/dissectors/packet-ieee80211-radio.h>
+
+#include <epan/color_filters.h>
+#include "frame_tvbuff.h"
+
+#include "color_utils.h"
+#include "qt_ui_utils.h"
+#include "wireshark_application.h"
+
+#ifdef Q_OS_WIN
+#include "wsutil/file_util.h"
+#include <QSysInfo>
+#endif
+
+#include <QPaintEvent>
+#include <QPainter>
+#include <QGraphicsScene>
+#include <QToolTip>
+
+#include "packet_list.h"
+#include "packet_list_model.h"
+
+#include "ui/main_statusbar.h"
+
+const float fraction = 0.8F;
+const float base = 0.1F;
+
+class pcolor : public QColor
+{
+public:
+ inline pcolor(float red, float green, float blue) : QColor(
+ (int) (255*(red * fraction + base)),
+ (int) (255*(green * fraction + base)),
+ (int) (255*(blue * fraction + base)) ) { }
+};
+
+static void reset_rgb(float rgb[TIMELINE_HEIGHT][3])
+{
+ int i;
+ for(i = 0; i < TIMELINE_HEIGHT; i++)
+ rgb[i][0] = rgb[i][1] = rgb[i][2] = 1.0;
+}
+
+static void render_pixels(QPainter &p, gint x, gint width, float rgb[TIMELINE_HEIGHT][3], float ratio)
+{
+ int previous = 0, i;
+ for(i = 1; i <= TIMELINE_HEIGHT; i++) {
+ if (i != TIMELINE_HEIGHT &&
+ rgb[previous][0] == rgb[i][0] &&
+ rgb[previous][1] == rgb[i][1] &&
+ rgb[previous][2] == rgb[i][2])
+ continue;
+ if (rgb[previous][0] != 1.0 || rgb[previous][1] != 1.0 || rgb[previous][2] != 1.0) {
+ p.fillRect(QRectF(x/ratio, previous, width/ratio, i-previous), pcolor(rgb[previous][0],rgb[previous][1],rgb[previous][2]));
+ }
+ previous = i;
+ }
+ reset_rgb(rgb);
+}
+
+static void render_rectangle(QPainter &p, gint x, gint width, guint height, int dfilter, float r, float g, float b, float ratio)
+{
+ p.fillRect(QRectF(x/ratio, TIMELINE_HEIGHT/2-height, width/ratio, dfilter ? height * 2 : height), pcolor(r,g,b));
+}
+
+static void accumulate_rgb(float rgb[TIMELINE_HEIGHT][3], int height, int dfilter, float width, float red, float green, float blue)
+{
+ int i;
+ for(i = TIMELINE_HEIGHT/2-height; i < (TIMELINE_HEIGHT/2 + (dfilter ? height : 0)); i++) {
+ rgb[i][0] = rgb[i][0] - width + width * red;
+ rgb[i][1] = rgb[i][1] - width + width * green;
+ rgb[i][2] = rgb[i][2] - width + width * blue;
+ }
+}
+
+
+void WirelessTimeline::mousePressEvent(QMouseEvent *event)
+{
+ start_x = last_x = event->localPos().x();
+}
+
+
+void WirelessTimeline::mouseMoveEvent(QMouseEvent *event)
+{
+ if (event->buttons() == Qt::NoButton)
+ return;
+
+ qreal offset = event->localPos().x() - last_x;
+ last_x = event->localPos().x();
+
+ qreal shift = ((qreal) (end_tsf - start_tsf))/width() * offset;
+ start_tsf -= shift;
+ end_tsf -= shift;
+ clip_tsf();
+
+ // TODO: scroll by moving pixels and redraw only exposed area
+ // render(p, ...)
+ // then update full widget only on release.
+ update();
+}
+
+
+void WirelessTimeline::mouseReleaseEvent(QMouseEvent *event)
+{
+ qreal offset = event->localPos().x() - start_x;
+
+ /* if this was a drag, ignore it */
+ if (std::abs(offset) > 3)
+ return;
+
+ /* this was a click */
+ guint num = find_packet(event->localPos().x());
+ if (num == 0)
+ return;
+
+ frame_data *fdata = frame_data_sequence_find(cfile.frames, num);
+ if (!fdata->flags.passed_dfilter && fdata->prev_dis_num > 0)
+ num = fdata->prev_dis_num;
+
+ cf_goto_frame(&cfile, num);
+}
+
+
+void WirelessTimeline::clip_tsf()
+{
+ // did we go past the start of the file?
+ if (start_tsf < first->start_tsf) {
+ // align the start of the file at the left edge
+ guint64 shift = first->start_tsf - start_tsf;
+ start_tsf += shift;
+ end_tsf += shift;
+ }
+ if (end_tsf > last->end_tsf) {
+ guint64 shift = end_tsf - last->end_tsf;
+ start_tsf -= shift;
+ end_tsf -= shift;
+ }
+}
+
+
+void WirelessTimeline::packetSelectionChanged()
+{
+ if (isHidden())
+ return;
+
+ if (cfile.current_frame) {
+ struct wlan_radio *wr = get_wlan_radio(cfile.current_frame->num);
+
+ guint left_margin = 0.9 * start_tsf + 0.1 * end_tsf;
+ guint right_margin = 0.1 * start_tsf + 0.9 * end_tsf;
+ guint half_window = (end_tsf - start_tsf)/2;
+
+ if (wr) {
+ // are we to the left of the left margin?
+ if (wr->start_tsf < left_margin) {
+ // scroll the left edge back to the left margin
+ guint64 offset = left_margin - wr->start_tsf;
+ if (offset < half_window) {
+ // small movement; keep packet to margin
+ start_tsf -= offset;
+ end_tsf -= offset;
+ } else {
+ // large movement; move packet to center of window
+ guint64 center = (wr->start_tsf + wr->end_tsf)/2;
+ start_tsf = center - half_window;
+ end_tsf = center + half_window;
+ }
+ } else if (wr->end_tsf > right_margin) {
+ guint64 offset = wr->end_tsf - right_margin;
+ if (offset < half_window) {
+ start_tsf += offset;
+ end_tsf += offset;
+ } else {
+ guint64 center = (wr->start_tsf + wr->end_tsf)/2;
+ start_tsf = center - half_window;
+ end_tsf = center + half_window;
+ }
+ }
+ clip_tsf();
+
+ update();
+ }
+ }
+}
+
+
+/* given an x position find which packet that corresponds to.
+ * if it's inter frame space the subsequent packet is returned */
+guint
+WirelessTimeline::find_packet(qreal x_position)
+{
+ guint64 x_time = start_tsf + (x_position/width() * (end_tsf - start_tsf));
+
+ return find_packet_tsf(x_time);
+}
+
+void WirelessTimeline::captureFileReadStarted(capture_file *cf)
+{
+ capfile = cf;
+ hide();
+ // TODO: hide or grey the toolbar controls
+}
+
+void WirelessTimeline::captureFileReadFinished()
+{
+ /* All frames must be included in packet list */
+ if (cfile.count == 0 || g_hash_table_size(radio_packet_list) != cfile.count)
+ return;
+
+ /* check that all frames have start and end tsf time and are reasonable time order.
+ * packet timing reference seems to be off a little on some generators, which
+ * causes frequent IFS values in the range 0 to -30. Some generators emit excessive
+ * data when an FCS error happens, and this results in the duration calculation for
+ * the error frame being excessively long. This can cause larger negative IFS values
+ * (-30 to -1000) for the subsequent frame. Ignore these cases, as they don't seem
+ * to impact the GUI too badly. If the TSF reference point is set wrong (TSF at
+ * start of frame when it is at the end) then larger negative offsets are often
+ * seen. Don't display the timeline in these cases.
+ */
+ /* TODO: update GUI to handle captures with occasional frames missing TSF data */
+ /* TODO: indicate error message to the user */
+ for (guint32 n = 1; n < cfile.count; n++) {
+ struct wlan_radio *w = get_wlan_radio(n);
+ if (w->start_tsf == 0 || w->end_tsf == 0) {
+ statusbar_push_temporary_msg("Packet number %u does not include TSF timestamp, not showing timeline.", n);
+ return;
+ }
+ if (w->ifs < -15000) {
+ statusbar_push_temporary_msg("Packet number %u has large negative jump in TSF, not showing timeline. Perhaps TSF reference point is set wrong?", n);
+ return;
+ }
+ }
+
+ first = get_wlan_radio(1);
+ last = get_wlan_radio(cfile.count);
+
+ start_tsf = first->start_tsf;
+ end_tsf = last->end_tsf;
+
+ /* TODO: only reset the zoom level if the file is changed, not on redissection */
+ zoom_level = 0;
+
+ show();
+ packetSelectionChanged();
+ // TODO: show or ungrey the toolbar controls
+ update();
+}
+
+void WirelessTimeline::appInitialized()
+{
+ register_tap_listener("wlan_radio_timeline", this, NULL, TL_REQUIRES_NOTHING, tap_timeline_reset, tap_timeline_packet, NULL/*tap_draw_cb tap_draw*/);
+}
+
+void WirelessTimeline::resizeEvent(QResizeEvent*)
+{
+ // TODO adjust scrollbar
+}
+
+
+// Calculate the x position on the GUI from the timestamp
+int WirelessTimeline::position(guint64 tsf, float ratio)
+{
+ int position = -100;
+
+ if (tsf != 0xffffffffffffffff) {
+ position = ((double) tsf - start_tsf)*width()*ratio/(end_tsf-start_tsf);
+ }
+ return position;
+}
+
+
+WirelessTimeline::WirelessTimeline(QWidget *parent, PacketList *packet_list) : QWidget(parent)
+{
+ setHidden(true);
+ this->packet_list = packet_list;
+ connect(packet_list->packetListModel(), SIGNAL(bgColorizationProgress(int,int)),
+ this, SLOT(bgColorizationProgress(int,int)));
+ connect(packet_list, SIGNAL(packetSelectionChanged()),
+ this, SLOT(packetSelectionChanged()));
+ connect(wsApp, SIGNAL(appInitialized()),
+ this, SLOT(appInitialized()));
+ zoom_level = 1.0;
+ setFixedHeight(TIMELINE_HEIGHT);
+ first_packet = 1;
+ setMouseTracking(true);
+
+ radio_packet_list = NULL;
+}
+
+void WirelessTimeline::tap_timeline_reset(void* tapdata)
+{
+ WirelessTimeline* timeline = (WirelessTimeline*)tapdata;
+
+ if (timeline->radio_packet_list != NULL)
+ {
+ g_hash_table_destroy(timeline->radio_packet_list);
+ }
+ timeline->hide();
+
+ timeline->radio_packet_list = g_hash_table_new(g_direct_hash, g_direct_equal);
+}
+
+gboolean WirelessTimeline::tap_timeline_packet(void *tapdata, packet_info* pinfo, epan_dissect_t* edt _U_, const void *data)
+{
+ WirelessTimeline* timeline = (WirelessTimeline*)tapdata;
+ struct wlan_radio *wlan_radio_info = (struct wlan_radio *)data;
+
+ /* Save the radio information in our own (GUI) hashtable */
+ g_hash_table_insert(timeline->radio_packet_list, GUINT_TO_POINTER(pinfo->num), wlan_radio_info);
+ return FALSE;
+}
+
+struct wlan_radio* WirelessTimeline::get_wlan_radio(guint32 packet_num)
+{
+ return (struct wlan_radio*)g_hash_table_lookup(radio_packet_list, GUINT_TO_POINTER(packet_num));
+}
+
+void WirelessTimeline::doToolTip(struct wlan_radio *wr, QPoint pos, int x)
+{
+ if (x < position(wr->start_tsf, 1.0)) {
+ QToolTip::showText(pos, QString("inter frame space %1 us").arg(wr->ifs));
+ } else {
+ QToolTip::showText(pos, QString("total duration %1 us\nNAV %2 us").arg(wr->end_tsf-wr->start_tsf)
+ .arg(wr->nav));
+ }
+}
+
+
+bool WirelessTimeline::event(QEvent *event)
+{
+ if (event->type() == QEvent::ToolTip) {
+ QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
+ guint packet = find_packet(helpEvent->pos().x());
+ if (packet) {
+ doToolTip(get_wlan_radio(packet), helpEvent->globalPos(), helpEvent->x());
+ } else {
+ QToolTip::hideText();
+ event->ignore();
+ }
+ return true;
+ }
+ return QWidget::event(event);
+}
+
+
+void WirelessTimeline::bgColorizationProgress(int first, int last)
+{
+ if (isHidden()) return;
+
+ struct wlan_radio *first_wr = get_wlan_radio(first);
+
+ struct wlan_radio *last_wr = get_wlan_radio(last-1);
+
+ int x = position(first_wr->start_tsf, 1);
+ int x_end = position(last_wr->end_tsf, 1);
+
+ update(x, 0, x_end-x+1, height());
+}
+
+
+guint64 WirelessTimeline::current_frame_center()
+{
+ if (cfile.current_frame == NULL)
+ return 0;
+
+ struct wlan_radio *wr = get_wlan_radio(cfile.current_frame->num);
+ return (wr->start_tsf + wr->end_tsf) /2;
+}
+
+
+void WirelessTimeline::zoom()
+{
+ guint64 center = current_frame_center();
+ int x_position = position(center, 1.0);
+
+ /* adjust the zoom around the selected packet */
+ float fraction = ((float) x_position)/width();
+ guint64 file_range = last->end_tsf - first->start_tsf;
+ guint64 span = pow(file_range, 1.0-zoom_level/25.0);
+ start_tsf = center - span*fraction;
+ end_tsf = center + span*(1.0-fraction);
+
+ /* if we go out of range for the whole file, clamp it */
+ if (start_tsf < first->start_tsf) {
+ end_tsf += first->start_tsf - start_tsf;
+ start_tsf = first->start_tsf;
+ } else if (end_tsf > last->end_tsf) {
+ start_tsf -= end_tsf - last->end_tsf;
+ end_tsf = last->end_tsf;
+ }
+
+ update();
+}
+
+void WirelessTimeline::zoomIn()
+{
+ zoom_level++;
+ zoom();
+}
+
+void WirelessTimeline::zoomOut()
+{
+ zoom_level--;
+ if (zoom_level < 0) zoom_level = 0;
+ zoom();
+}
+
+void WirelessTimeline::zoomFullOut()
+{
+ zoom_level = 0;
+ zoom();
+}
+
+int WirelessTimeline::find_packet_tsf(guint64 tsf)
+{
+ if (cfile.count < 1)
+ return 0;
+
+ if (cfile.count < 2)
+ return 1;
+
+ guint32 min_count = 1;
+ guint32 max_count = cfile.count-1;
+
+ guint64 min_tsf = get_wlan_radio(min_count)->end_tsf;
+ guint64 max_tsf = get_wlan_radio(max_count)->end_tsf;
+
+ for(;;) {
+ if (tsf >= max_tsf)
+ return max_count+1;
+
+ if (tsf < min_tsf)
+ return min_count;
+
+ guint32 middle = (min_count + max_count)/2;
+ if (middle == min_count)
+ return middle+1;
+
+ guint64 middle_tsf = get_wlan_radio(middle)->end_tsf;
+
+ if (tsf >= middle_tsf) {
+ min_count = middle;
+ min_tsf = middle_tsf;
+ } else {
+ max_count = middle;
+ max_tsf = middle_tsf;
+ }
+ };
+}
+
+void
+WirelessTimeline::paintEvent(QPaintEvent *qpe)
+{
+ QPainter p(this);
+
+ // painting is done in device pixels in the x axis, get the ratio here
+ float ratio = p.device()->devicePixelRatio();
+
+ unsigned int packet;
+ double zoom;
+ int last_x=-1;
+ int left = qpe->rect().left()*ratio;
+ int right = qpe->rect().right()*ratio;
+ float rgb[TIMELINE_HEIGHT][3];
+ reset_rgb(rgb);
+
+ zoom = ((double) width())/(end_tsf - start_tsf) * ratio;
+
+ /* background is light grey */
+ p.fillRect(0, 0, width(), TIMELINE_HEIGHT, QColor(240,240,240));
+
+ /* background of packets visible in packet_list is white */
+ int top = packet_list->indexAt(QPoint(0,0)).row();
+ int bottom = packet_list->indexAt(QPoint(0,packet_list->viewport()->height())).row();
+ PacketListModel *model = packet_list->packetListModel();
+ int x1 = top == -1 ? 0 : position(get_wlan_radio(model->getRowFdata(top)->num)->start_tsf, ratio);
+ int x2 = bottom == -1 ? width() : position(get_wlan_radio(model->getRowFdata(bottom)->num)->end_tsf, ratio);
+ p.fillRect(QRectF(x1/ratio, 0, (x2-x1+1)/ratio, TIMELINE_HEIGHT), Qt::white);
+
+ /* background of current packet is blue */
+ if (cfile.current_frame) {
+ struct wlan_radio *wr = get_wlan_radio(cfile.current_frame->num);
+ if (wr) {
+ x1 = position(wr->start_tsf, ratio);
+ x2 = position(wr->end_tsf, ratio);
+ p.fillRect(QRectF(x1/ratio, 0, (x2-x1+1)/ratio, TIMELINE_HEIGHT), Qt::blue);
+ }
+ }
+
+ QGraphicsScene qs;
+ for(packet = find_packet_tsf(start_tsf + left/zoom - 40000); packet <= cfile.count; packet++) {
+ frame_data *fdata = frame_data_sequence_find(cfile.frames, packet);
+ struct wlan_radio *ri = get_wlan_radio(fdata->num);
+ float x, width, red,green,blue;
+ gint8 rssi = ri->aggregate ? ri->aggregate->rssi : ri->rssi;
+ guint height = (rssi+100)/2;
+ gint end_nav;
+
+ if (ri == NULL) continue;
+
+ /* leave a margin above the packets so the selected packet can be seen */
+ if (height > TIMELINE_HEIGHT/2-6)
+ height = TIMELINE_HEIGHT/2-6;
+
+ /* ensure shortest packets are clearly visible */
+ if (height < 2)
+ height = 2;
+
+ /* skip frames we don't have start and end data for */
+ /* TODO: show something, so it's clear a frame is missing */
+ if (ri->start_tsf == 0 || ri->end_tsf == 0)
+ continue;
+
+ x = ((gint64) (ri->start_tsf - start_tsf))*zoom;
+ /* is there a previous anti-aliased pixel to output */
+ if (last_x >= 0 && ((int) x) != last_x) {
+ /* write it out now */
+ render_pixels(p, last_x, 1, rgb, ratio);
+ last_x = -1;
+ }
+
+ /* does this packet start past the right edge of the window? */
+ if (x >= right) {
+ break;
+ }
+
+ width = (ri->end_tsf - ri->start_tsf)*zoom;
+ if (width < 0) {
+ continue;
+ }
+
+ /* is this packet completely to the left of the displayed area? */
+ // TODO clip NAV line properly if we are displaying it
+ if ((x + width) < left)
+ continue;
+
+ /* remember the first displayed packet */
+ if (first_packet < 0)
+ first_packet = packet;
+
+ if (fdata->color_filter) {
+ const color_t *c = &((color_filter_t *) fdata->color_filter)->fg_color;
+ red = c->red / 65535.0;
+ green = c->green / 65535.0;
+ blue = c->blue / 65535.0;
+ } else {
+ red = green = blue = 0.0;
+ }
+
+ /* record NAV field at higher magnifications */
+ end_nav = x + width + ri->nav*zoom;
+ if (zoom >= 0.01 && ri->nav && end_nav > 0) {
+ gint y = 2*(packet % (TIMELINE_HEIGHT/2));
+ qs.addLine(QLineF((x+width)/ratio, y, end_nav/ratio, y), QPen(pcolor(red,green,blue)));
+ }
+
+ /* does this rectangle fit within one pixel? */
+ if (((int) x) == ((int) (x+width))) {
+ /* accumulate it for later rendering together
+ * with all other sub pixels that fall within this
+ * pixel */
+ last_x = x;
+ accumulate_rgb(rgb, height, fdata->flags.passed_dfilter, width, red, green, blue);
+ } else {
+ /* it spans more than 1 pixel.
+ * first accumulate the part that does fit */
+ float partial = ((int) x) + 1 - x;
+ accumulate_rgb(rgb, height, fdata->flags.passed_dfilter, partial, red, green, blue);
+ /* and render it */
+ render_pixels(p, (int) x, 1, rgb, ratio);
+ last_x = -1;
+ x += partial;
+ width -= partial;
+ /* are there any whole pixels of width left to draw? */
+ if (width > 1.0) {
+ render_rectangle(p, x, width, height, fdata->flags.passed_dfilter, red, green, blue, ratio);
+ x += (int) width;
+ width -= (int) width;
+ }
+ /* is there a partial pixel left */
+ if (width > 0.0) {
+ last_x = x;
+ accumulate_rgb(rgb, height, fdata->flags.passed_dfilter, width, red, green, blue);
+ }
+ }
+ }
+
+ // draw the NAV lines last, so they appear on top of the packets
+ qs.render(&p, rect(), rect());
+}
+
+
+/*
+ * 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/wireless_timeline.h b/ui/qt/wireless_timeline.h
new file mode 100644
index 0000000000..628524e4b4
--- /dev/null
+++ b/ui/qt/wireless_timeline.h
@@ -0,0 +1,122 @@
+/* wireless_timeline.h
+ * GUI to show an 802.11 wireless timeline of packets
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * Copyright 2012 Parc Inc and Samsung Electronics
+ * Copyright 2015, 2016 & 2017 Cisco Inc
+ *
+ * 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 <QScrollArea>
+
+#ifndef WIRELESSTIMELINE_H
+#define WIRELESSTIMELINE_H
+
+#include <stdio.h>
+
+#include <config.h>
+
+#include <glib.h>
+
+#include "file.h"
+
+#include "ui/ui_util.h"
+
+#include <epan/prefs.h>
+#include <epan/plugin_if.h>
+#include <epan/timestamp.h>
+
+#include <epan/dissectors/packet-ieee80211-radio.h>
+
+#include <QScrollArea>
+
+#include "cfile.h"
+
+/* pixels height for rendered timeline */
+#define TIMELINE_HEIGHT 64
+
+class WirelessTimeline;
+class PacketList;
+
+class WirelessTimeline : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit WirelessTimeline(QWidget *parent, PacketList *packet_list);
+ void captureFileReadStarted(capture_file *cf);
+ void captureFileReadFinished();
+ void zoomIn();
+ void zoomOut();
+ void zoomFullOut();
+
+protected:
+ void resizeEvent(QResizeEvent *event);
+ void paintEvent(QPaintEvent *event);
+ void mousePressEvent (QMouseEvent *event);
+ void mouseMoveEvent (QMouseEvent *event);
+ void mouseReleaseEvent (QMouseEvent *event);
+ bool event(QEvent *event);
+
+public slots:
+ void bgColorizationProgress(int first, int last);
+ void packetSelectionChanged();
+ void appInitialized();
+
+protected:
+ static void tap_timeline_reset(void* tapdata);
+ static gboolean tap_timeline_packet(void *tapdata, packet_info* pinfo, epan_dissect_t* edt, const void *data);
+
+ struct wlan_radio* get_wlan_radio(guint32 packet_num);
+
+ void clip_tsf();
+ guint64 current_frame_center();
+ int position(guint64 tsf, float ratio);
+ int find_packet_tsf(guint64 tsf);
+ void doToolTip(struct wlan_radio *wr, QPoint pos, int x);
+ void zoom();
+ int zoom_level;
+ qreal start_x, last_x;
+ PacketList *packet_list;
+ guint find_packet(qreal x);
+ float rgb[TIMELINE_HEIGHT][3];
+
+ guint64 start_tsf;
+ guint64 end_tsf;
+ int first_packet; /* first packet displayed */
+ struct wlan_radio *first, *last;
+ capture_file *capfile;
+
+ GHashTable* radio_packet_list;
+};
+
+#endif // WIRELESS_TIMELINE_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:
+ */