Huge thanks to our Platinum Members Endace and LiveAction,
and our Silver Member Veeam, for supporting the Wireshark Foundation and project.

Ethereal-dev: Re: [Ethereal-dev] Ethereal addition for analysing RTP data

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Miha Jemec <m.jemec@xxxxxxxxxxx>
Date: Thu, 06 Mar 2003 17:04:35 +0100
Ok, I made a few changes.

I putted g711.c in ethereal/ and added g711.h with declarations in ethereal/
There was also a line (I forgot about it yesterday) in gtk/menu.c:
#include "tap_rtp.c"

Since this is a similar problem, I created tap_rtp.h with the declaration for rtp_analyse_cb() and changed menu.c (#include "tap_rtp.c" to #include "tap_rtp'h") and tap_rtp.c

So the modified attached files are:
gtk/tap_rtp.c
gtk/tap_rtp.h
gtk/menu.c
g711.h

I had to change the Makefile (added g711.c and .h) and gtk/Makefile (added tap_rtp.c and .h), but I don't know how to do it with Makefile.am and autoconf ... So I can't make a diff for this.

Regards,
Miha

(Since I went through the whole tap and README.tapping staff it won't be difficult for me to find things that were unclear and could be explained clearer. I will post you a message with some sugestions)


Ronnie Sahlberg wrote:

Hi, looks good

very minor changes i would like to see:

put g711.c in ethereal/ instead of ethereal/gtk/ since later someone may
want to use the feature also for
tethereal.
create g711.h which holds the prototypes so you dont include the .c file in
the actual tap file.

apart from that, it looks good to me.

fix these issues and i will check it in.


best regards
    ronnie sahlberg


also, comments on what is unclear and what can be improved in README.tapping
are most welcome

----- Original Message -----
From: "Miha Jemec"
Sent: Thursday, March 06, 2003 2:18 AM
Subject: [Ethereal-dev] Ethereal addition for analysing RTP data


Hi!

After quite some time here is the rewritten version of RTP analysing
tool. It uses now the tap system (I had to change the packet-rtp.c and
packet-rtp.h) and with the intention of being OS independent it doesn't
make possible to play the voice directly through the sound card but has
the possibility to save it in a file for later listening. How it works
is described in code itself (I tried to put many comments there). There
are still some open questions or problems (look for XXX). I was able to
test it only under Intel machine with Linux OS, so test on other OS
would be welcome.

Attached files are:

rtp.jpg - screenshot
tap_rtp.c - actual code, that goes into gtk/ directory
g711.c - routines for converting alaw or ulaw to linear, goes into gtk/
as well

I also modified some other files (ethereal 0.9.8):

in gtk/menu.c one more line under "Tools":

ITEM_FACTORY_ENTRY("/Tools/_RTP analysis...", NULL, rtp_analyse_cb, 0,
NULL, NULL),


in packet-rtp.h added following structure at the end:

struct _rtp_info {
         gboolean    info_padding_set;
         gboolean    info_marker_set;
         unsigned int info_payload_type;
         unsigned int info_padding_count;
         guint16     info_seq_num;
         guint32     info_timestamp;
         guint32     info_sync_src;
         guint       info_data_len;
};


in packet-rtp.c: diff is attached


Because I'm not very comfortable with automake , autoconf, ... I just
added some lines by hand to compile:

In etereal_tap_register two more lines:

 { extern void register_tap_listener_gtkrtp (void);
     register_tap_listener_gtkrtp ();}

In gtk/Makefile in line am__objects_1 =
 added  tap_rtp.$(OBJEXT)


I would be glad to get any comments.
Regards,
Miha





_______________________________________________
Ethereal-dev mailing list
Ethereal-dev@xxxxxxxxxxxx
http://www.ethereal.com/mailman/listinfo/ethereal-dev

/* 
 * g711.h
 *
 * Definitions for routines for u-law, A-law and linear PCM conversions
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

unsigned char linear2alaw( int );
int alaw2linear( unsigned char );
unsigned char linear2ulaw( int );
int ulaw2linear( unsigned char ); 
/*
 * tap_rtp.h
 *
 * Declaration for rtp analysing tool
 *
 * Copyright 2003, Iskratel, Ltd, Kranj
 * By Miha Jemec <m.jemec@xxxxxxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
void rtp_analyse_cb(GtkWidget *, gpointer);
/*
 * tap_rtp.c
 *
 * RTP analysing addition  for ethereal
 *
 * Copyright 2003, Iskratel, Ltd, Kranj
 * By Miha Jemec <m.jemec@xxxxxxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * This tap works as follows:
 * When the user clicks on the RTP analisys button, we first check if it is a RTP packet.
 * If yes we store the SSRC, ip, and port values. Then the tap is registered and the 
 * redissect_packets() routine is called. So we go through all the RTP packets and search 
 * for SSRC of reversed connection (it has inversed socket parameters). If more than one 
 * is found a window is displayed where the user can select the appropriate from the list.
 * Rigth now we have the information about the converstion we are looking for (both SSRC). 
 * The redissect_packets() routine is called again. This time whenever a RTP packet with
 * matching SSRC values arrives, we store all the information we need (number, sequence
 * number, arrival time, ...) and compute the delay, jitter and wrong sequence number.
 * We add this values to CList. If the RTP packet carries voice in g711 alaw or ulaw, we
 * also store this voice information in a temp file. Window is displayed.
 * Then three buttons are available: Close, Refresh and Save voice.
 * The Refresh button calls the redissect_packets() routine again. It goes through the packets
 * again and does all the calculation again (if capturing in real time this means that some
 * more packets could come and can be computed in statistic). It also writes the sound
 * data again.
 * The Save voice button opens the dialog where we can choose the file name, format (not yet)
 * and direction we want to save. Currently it works only with g711 alaw and ulaw, and if the
 * length of captured packets is equal the length of packets on wire and if there are no padding
 * bits.    
 *
 * To do:
 * - Support for saving voice in more different formats and with more different codecs:
 *   Since this should be portable to all OS, there is only possibility to save the 
 *   voice in a file and not play it directly through the sound card. There are enough 
 *   players on all platforms, that are doing right this. What about the format? 
 *   Currently there is only support for saving as an .au file (ulaw, 8000 Hz, 8bit)
 *   There are many players for this format on all platforms (for example Windows Media Player
 *   under Windows, command play under Linux). Support will be added for wav format and 
 *   possibility to save with two channels (separate channel for each direction)
 *
 * - Support for more codecs. Now you can save voice only if the codec is g.711 alaw or ulaw.
 *
 * - right now, the reversed connection must have the same (only inversed) ip and port numbers.
 *   I think that there is no reason that in special cases the reversed connection would not use 
 *   some different port or even the IP combination (please correct me if I am wrong). 
 *   So this will be added soon.
 *
 * - some more statistics (delay and jitter distribution)
 *
 * - GTK2 implementation
 *
 * - grammar correction
 * 
 * - some more testing (other OS)
 *
 * XXX Problems: 
 *
 * - how to use snprintf (or g_snprintf) with guint16, guint32 ? If I put %lu for guint32 
 *   then compiler makes a warning but it works. If I put %d for guint32, 
 *   then compiler doesn't warns, but then it doesn't work
 *
 * - instead of tmpnam() use of mkstemp(). 
 *   I tried to do it with mkstemp() but didn't now how to solve following  problem: 
 *   I call mkstemp() and then write in this temp file and it works fine . But if the user 
 *   then hits the refresh button, this temp file should be deleted and opened again. I tried
 *   to call close() and unlink(), but when I call mkstemp() for the second time I always get
 *   an error ( -1) as return value. What is the correct order? Is it possible to call 
 *   mkstemp() twice with the same template?    
 *
 * - problem with statistics for lost (late, duplicated) packets. How to make the statistic 
 *   more resistant to special (bizarre) arrival of sequence numbers
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#include <gtk/gtk.h>
#include "tap_rtp.h"
#include "globals.h"
#include <string.h>
#include "epan/packet_info.h"
#include <epan/epan_dissect.h>
#include <epan/filesystem.h>
#include "../tap.h"
#include "../register.h"
#include "../packet-rtp.h"
#include "file_dlg.h"
#include "dlg_utils.h"
#include "ui_util.h"
#include "simple_dialog.h"
#include "main.h"
#include <math.h>
#include "progress_dlg.h"
#include "compat_macros.h"
#include "../g711.h"
#include <unistd.h>
#include <fcntl.h>

static GtkWidget *rtp_w = NULL;
static GtkWidget *voice_w = NULL;
static GtkWidget *save_w = NULL;
static GtkWidget *main_vb;
static GtkWidget *clist;
static GtkWidget *clist_r;
static GtkWidget *max;
static GtkWidget *max_r;

static gboolean copy_file(gchar *, /*gint,*/ gint, void *);

char f_tempname[100], r_tempname[100];

/* type of error when saving voice in a file didn't succeed */
typedef enum {
	WRONG_CODEC,
	WRONG_LENGTH,
	PADDING_SET,
	FILE_OPEN_ERROR,
	NO_DATA
} error_type_t; 

/* structure that holds the information about the forwarding and reversed connection */
/* f_* always aplies to the forward direction and r_* to the reversed */
typedef struct _info_stat {
	gchar source[16];
	gchar destination[16];
	guint16 srcport;
	guint16 dstport;
	guint32 ssrc_forward;
	guint32 ssrc_reversed;
	guint32 *ssrc_tmp;
	gboolean search_ssrc;
	guint reversed_ip;
	guint reversed_ip_and_port;
	gboolean f_first_packet;
	gboolean r_first_packet;
	guint16 f_seq_num;
	guint16 r_seq_num;
	guint32 f_timestamp;
	guint32 r_timestamp;
	guint32 f_delta_timestamp;
	guint32 r_delta_timestamp;
	double f_delay;
	double r_delay;
	double f_jitter;
	double r_jitter;
	double f_time;
	double r_time;
	double f_start_time;
	double r_start_time;
	double f_max_delay;
	double r_max_delay;
	guint32 f_max_nr;
	guint32 r_max_nr;
	guint16 f_start_seq_nr;
	guint16 r_start_seq_nr;
	guint16 f_stop_seq_nr;
	guint16 r_stop_seq_nr;
	guint32 f_total_nr;
	guint32 r_total_nr;
	guint32 f_sequence;
	guint32 r_sequence;
	gint f_cycles;
	gint r_cycles;
	gboolean f_under;
	gboolean r_under;
	FILE *f_fp;
	FILE *r_fp;
	gboolean f_saved;
	gboolean r_saved;
	error_type_t f_error_type;
	error_type_t r_error_type;
	guint32 f_count;
	guint32 r_count;
} info_stat;


/* when there is a [re]reading of packet's */
static void
rtp_reset(void *prs)
{
  info_stat *rs=prs;

  rs->f_first_packet = TRUE;
  rs->r_first_packet = TRUE;
  rs->f_max_delay = 0;
  rs->r_max_delay = 0;
  rs->f_max_nr = 0;
  rs->r_max_nr = 0;
  rs->f_total_nr = 0;
  rs->r_total_nr = 0;
  rs->f_sequence = 0;
  rs->r_sequence = 0;
  rs->f_start_seq_nr = 0;
  rs->r_start_seq_nr = 1; /* 1 is ok (for statistics in reversed direction) */
  rs->f_stop_seq_nr = 0;
  rs->r_stop_seq_nr = 0;
  rs->f_cycles = 0;
  rs->r_cycles = 0;
  rs->f_under = FALSE;
  rs->r_under = FALSE;
  rs->f_saved = FALSE;
  rs->r_saved = FALSE;
  rs->f_start_time = 0;
  rs->r_start_time = 0;
  rs->f_count = 0;
  rs->r_count = 0;
  /* XXX check for error at fclose? */
  if (rs->f_fp != NULL)
	fclose(rs->f_fp); 
  if (rs->r_fp != NULL)
	fclose(rs->r_fp); 
  rs->f_fp = fopen(f_tempname, "w"); 
  if (rs->f_fp == NULL)
	rs->f_error_type = FILE_OPEN_ERROR;
  rs->r_fp = fopen(r_tempname, "w");
  if (rs->r_fp == NULL)
	rs->r_error_type = FILE_OPEN_ERROR;
  return;
}

/* here we can redraw the output */
/* not used yet */
static void rtp_draw(void *prs _U_)
{
	return;
}

/* when we are finished with redisection, we add the label for the statistic */
static void draw_stat(void *prs)
{
	info_stat *rs=prs;
	gchar label_max[200];
	guint32 f_expected = (rs->f_stop_seq_nr + rs->f_cycles*65536) - rs->f_start_seq_nr + 1;
	guint32 r_expected = (rs->r_stop_seq_nr + rs->r_cycles*65536) - rs->r_start_seq_nr + 1;
	gint32 f_lost = f_expected - rs->f_total_nr;
	gint32 r_lost = r_expected - rs->r_total_nr;

	g_snprintf(label_max, 199, "Max delay = %f sec at packet nr. %lu \n\n"
		"Total RTP packets = %lu   (expected %lu)   Lost RTP packets = %ld"  
		"   Sequence error = %lu",
			rs->f_max_delay, rs->f_max_nr, rs->f_total_nr, f_expected, 
							f_lost, rs->f_sequence);

	gtk_label_set_text(GTK_LABEL(max), label_max);

	g_snprintf(label_max, 199, "Max delay = %f sec at packet nr. %lu \n\n"
		"Total RTP packets = %lu   (expected %lu)   Lost RTP packets = %ld"
		"   Sequence error = %lu",
			 rs->r_max_delay, rs->r_max_nr, rs->r_total_nr, r_expected,
							r_lost, rs->r_sequence);

	gtk_label_set_text(GTK_LABEL(max_r), label_max);

	/* could be done somewhere else, but can be here as well */
	/* if this is true, then we don't have any reversed connection, so the error type
	 * will be no data. This applies only the reversed connection */
	if (rs->reversed_ip_and_port == 0)
		rs->r_error_type = NO_DATA;

	return ;
}

/* append a line to clist */
/* XXX is there a nicer way to make these assignements? */
static void add_to_clist(gboolean forward, guint32 number, guint16 seq_num, 
				double delay, double jitter, gboolean status, gboolean marker)
{
	gchar *data[6];
	gchar field[6][30];

	data[0]=&field[0][0];
	data[1]=&field[1][0];
	data[2]=&field[2][0];
	data[3]=&field[3][0];
	data[4]=&field[4][0];
	data[5]=&field[5][0];

	g_snprintf(field[0], 20, "%lu", number);
	g_snprintf(field[1], 20, "%lu", seq_num);
	g_snprintf(field[2], 20, "%f", delay);
	g_snprintf(field[3], 20, "%f", jitter);
	g_snprintf(field[4], 20, "%s", marker? "SET" : "");
	g_snprintf(field[5], 29, "%s", status? "OK" : "NOK - Wrong sequence nr.");

	gtk_clist_append(GTK_CLIST(forward? clist : clist_r), data);

}

/* whenever a RTP packet is seen by the tap listener */
/* this function works as follows:
 * 1) packets that are not displayed are ignored
 *	return
 * 2) are we searching what could be the reversed connection (looking for reversed SSRC)
 *	if yes, do the parameters match (inversed IP and port combination from the forward one)?
 *		if yes, do we already have this SSRC stored
 *			if not store it
 * 3) if not, is current packet matching the forward direction
 *	is it the first time we see a packet in this direction
 *		if yes, store some values, add a line to list and save the voice info
 *		in a temporary file if the codec is supported and the RTP data is ok
 *	if not, is it a packet with mark bit set (there was silence surpression)
 *		same as above, only we have to add some silence in front of the voice data
 *	if not, then this must be a normal packet
 *		store the values and voice data
 * 4) if not, is current packet matching the reversed connection
 *	(same as for number 3)
 */
static int rtp_packet(void *prs, packet_info *pinfo, epan_dissect_t *edt _U_, struct _rtp_info *pri)
{
	info_stat *rs=prs;
	guint i;
	double n_time;
	double n_jitter;
	guint8 *data;
	gint16 tmp;

	/* we ignore packets that are not displayed */
	if (pinfo->fd->flags.passed_dfilter == 0)
		return 0;

	/* are we looking for the SSRC of the reversed connection? */
	if (rs->search_ssrc != FALSE) {
		/* XXX what should be the rules for reversed connection? 
		 * 1. It should should have same inversed IP and port numbers
		 * 2. If none are found, only inversed IP's - is this possible?
		 * 3. If none are found, there isn't any reversed connection 
		 * XXX is it possible that the conversation is not P2P? 
		 * Curretly it works only if it matches the number 1. */

		/* have we found inverse parameters? */
		if ( strcmp(ip_to_str(pinfo->src.data), rs->destination) == 0  && 
				strcmp( ip_to_str(pinfo->dst.data), rs->source) == 0 ) {

			/* do the ports also match? */
			if ((rs->srcport == pinfo->destport) && (rs->dstport == pinfo->srcport)) {
				/* ok, the ip and port combination does match 
				 * do we already have this ssrc stored */
				for(i=0; i< rs->reversed_ip_and_port; i++) {
					if (pri->info_sync_src == *(rs->ssrc_tmp+i) ) 
						return 0;
				}
				
				/* no, we found new ssrc, let's store it */
				rs->ssrc_tmp = (guint32*)g_realloc(rs->ssrc_tmp, 
								(i+1)*sizeof(guint32));
				*(rs->ssrc_tmp+i) = pri->info_sync_src;
				rs->reversed_ip_and_port++;
				return 0;
			}
			/* no, only ip addresses match */
			/* XXX not implemented yet */
			else {
				rs->reversed_ip++;
				return 0;
			}

		}
	}
	
	/* ok, we are not looking for SSRC of the reversed connection */
	/* is it the forward direction? 
	 * if yes, there 3 possibilities:
	 * a) is this the first packet we got in this direction?
	 * b) or is it a packet with the mark bit set?
	 * c) if neither then it is a "normal" packet */
	else if (rs->ssrc_forward == pri->info_sync_src) {
		/* first packet? */
		if (rs->f_first_packet != FALSE) {
			/* we store all the values */
			rs->f_seq_num = pri->info_seq_num;
			rs->f_delay = 0;
			rs->f_jitter = 0;
			rs->f_first_packet = FALSE;
			rs->f_timestamp = pri->info_timestamp;
			rs->f_start_seq_nr = pri->info_seq_num;
			rs->f_stop_seq_nr = pri->info_seq_num;
			rs->f_total_nr++;
			rs->f_time = (double)pinfo->fd->rel_secs + 
						(double) pinfo->fd->rel_usecs/1000000;
			rs->f_start_time = rs->f_time;
			/* and add a row to clist; delay and jitter are 0 for the first packet */
			add_to_clist(TRUE, pinfo->fd->num, pri->info_seq_num, 0, 0, TRUE, FALSE);

			/* and now save the voice info */

			/* if we couldn't open the tmp file for writing, then we set the flag */
			if (rs->f_fp == NULL) {
				rs->f_saved = FALSE;
				rs->f_error_type = FILE_OPEN_ERROR;
				return 0;
			}
			/* if the captured length and packet length aren't equal, we quit 
			 * because there is some information missing */
			if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
				rs->f_saved = FALSE;
				rs->f_error_type = WRONG_LENGTH;
				return 0;
			}
			/* if padding bit is set, we don't do it yet */
			if (pri->info_padding_set != FALSE) {
				rs->f_saved = FALSE;
				rs->f_error_type = PADDING_SET;
				return 0;
			}
			/* is it the ulaw? */
			if (pri->info_payload_type == 0) {
				/* we put the pointer at the beggining of the RTP data, that is
				 * at the end of the current frame minus the length of the 
				 * RTP field plus 12 for the RTP header */
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len-12 ); i++, data++) {
					tmp = (gint16 )ulaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->f_fp);
					rs->f_count++;
				}
				rs->f_saved = TRUE;
				return 0;
			}
			/* alaw? */
			else if (pri->info_payload_type == 8) {
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len -12 ); i++, data++) {
					tmp = (gint16 )alaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->f_fp);
					rs->f_count++;
				}
				rs->f_saved = TRUE;
				return 0;
			}
			/* unsupported codec or other error */
			else {
				rs->f_saved = FALSE;
				rs->f_error_type = WRONG_CODEC;
				return 0;
			}
		}
		
		/* packet with mark bit set? */
		if (pri->info_marker_set != FALSE) {
			n_time = (double)pinfo->fd->rel_secs +
					(double) pinfo->fd->rel_usecs/1000000;
			/* jitter is calculated as for RCTP - RFC 1889 
			 * J = J + ( | D(i-1, i) | - J) / 16
			 * XXX the output there should be in timestamp (probably miliseconds)
			 * units expressed as an unsigned integer, so should we do it the same? 
			 * (currently we use seconds) 
			 *
			 * XXX Packet loss in RTCP is calculated as the difference between the
			 * number of packets expected and actually received, where for actually 
			 * received the number is simply the count of packets as they arrive, 
			 * including any late or duplicate packets (this means that the number
			 * can be negative). For example, if the seq numbers of the arrived
			 * packets are: 1,2,3,4,5,5,7,7,9,10 the expected number is 10 and the
			 * the number of actually captured frames is also 10. So in upper 
			 * calculation there would be no losses. But there are 2 losses and 
			 * 2 duplicate packets. Because this kind of statistic is rather 
			 * useless (or confusing) we add the information, that there was 
			 * an error with sequence number each time the sequence number was 
			 * not one bigger than the previous one
			*/
                       
			/* jitter calculation */
			n_jitter = rs->f_jitter + ( fabs(n_time-(rs->f_time) - 
					((double)(pri->info_timestamp)-
					(double)(rs->f_timestamp))/8000) - rs->f_jitter)/16;

			/* we add the information into the clist */
			add_to_clist(TRUE, pinfo->fd->num, pri->info_seq_num, n_time-(rs->f_time),
				 n_jitter, rs->f_seq_num+1 == pri->info_seq_num?TRUE:FALSE, TRUE);

			/* when calculating expected rtp packets the seq number can wrap around
			 * so we have to count the number of cycles 
			 * f_cycles counts the wraps around in forwarding connection and
			 * f_under is flag that indicates where we are 
			 *
			 * XXX how to determine number of cycles with all possible lost, late
			 * and duplicated packets without any doubt? It seems to me, that 
			 * because of all possible combination of late, duplicated or lost
			 * packets, this can only be more or less good approximation
			 *
			 * There are some combinations (rare but theoretically possible), 
			 * where below code won't work correctly - statistic may be wrong then.
			 */

			/* so if the current sequence number is less than the start one
			 * we assume, that there is another cycle running */
			if ((pri->info_seq_num < rs->f_start_seq_nr) && (rs->f_under == FALSE)){
				rs->f_cycles++;
				rs->f_under = TRUE;
			}
			/* what if the start seq nr was 0. Then the above condition will never 
			 * be true, so we add another condition. XXX The problem would arise if
			 * if one of the packets with seq nr 0 or 65535 would be lost or late */
			else if ((pri->info_seq_num == 0) && (rs->f_stop_seq_nr == 65535) && 
									(rs->f_under == FALSE)){
				rs->f_cycles++;
				rs->f_under = TRUE;
			}
			/* the whole round is over, so reset the flag */
			else if ((pri->info_seq_num>rs->f_start_seq_nr)&&(rs->f_under!=FALSE)){
				rs->f_under = FALSE;
			}

			/* number of times where sequence number was not ok */
			if ( rs->f_seq_num+1 == pri->info_seq_num)
				rs->f_seq_num = pri->info_seq_num;
			/* XXX same problem as above */
			else if ( (rs->f_seq_num == 65535) && (pri->info_seq_num == 0) )
				rs->f_seq_num = pri->info_seq_num;
			/* lost packets */
			else if (rs->f_seq_num+1 < pri->info_seq_num) {
				rs->f_seq_num = pri->info_seq_num;
				rs->f_sequence++;
			}
			/* late or duplicated */
			else if (rs->f_seq_num+1 > pri->info_seq_num)
				rs->f_sequence++;

			rs->f_stop_seq_nr = pri->info_seq_num;
			rs->f_time = n_time;
			rs->f_jitter = n_jitter;
			rs->f_delta_timestamp = pri->info_timestamp - rs->f_timestamp;
			rs->f_timestamp = pri->info_timestamp;
			rs->f_total_nr++;

			/* save the voice information */
			/* if there was already an error, we quit */
			if (rs->f_saved == FALSE)
				return 0;
			/* if the captured length and packet length aren't equal, we quit */
			if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
				rs->f_saved = FALSE;
				rs->f_error_type = WRONG_LENGTH;
				return 0;
			}
			/* if padding bit is set, we don't do it yet */
			if (pri->info_padding_set != FALSE) {
				rs->f_saved = FALSE;
				rs->f_error_type = PADDING_SET;
				return 0;
			}
			/* because the mark bit is set, we have to add some silence in front */
			/* is it the ulaw? */
			if (pri->info_payload_type == 0) {
				/* we insert some silence */
				/* XXX the amount of silence should be the difference between
				 * the last timestamp and the current one minus x in the
				 * I am not sure if x is equal the amount of information 
				 * current packet? */
				for(i=0; i<(rs->f_delta_timestamp-pri->info_data_len+12); i++) {
					tmp = (gint16 )ulaw2linear((unsigned char)(0x55));
					fwrite(&tmp, 2, 1, rs->f_fp);
					rs->f_count++;
				}
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len-12 ); i++, data++) {
					tmp = (gint16 )ulaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->f_fp);
					rs->f_count++;
				}
				return 0;
			}
			/* alaw? */
			else if (pri->info_payload_type == 8) {
				for(i=0; i < (rs->f_delta_timestamp-pri->info_data_len+12); i++) {
					tmp = (gint16 )ulaw2linear((unsigned char)(0x55));
					fwrite(&tmp, 2, 1, rs->f_fp);
					rs->f_count++;
				}
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len -12 ); i++, data++) {
					tmp = (gint16 )alaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->f_fp);
					rs->f_count++;
				}
				return 0;
			}
			/* unsupported codec or other error */
			else {
				rs->f_saved = FALSE;
				rs->f_error_type = WRONG_CODEC;
				return 0;
			}
			return 0;
		}
		
		/* normal packet in forward connection */
		n_time = (double)pinfo->fd->rel_secs +
					(double) pinfo->fd->rel_usecs/1000000;
		n_jitter = rs->f_jitter + ( fabs (n_time-(rs->f_time) - 
				((double)(pri->info_timestamp)-
				(double)(rs->f_timestamp))/8000) - rs->f_jitter)/16;
		rs->f_delay =  n_time-(rs->f_time);
		/* the delay is bigger than previous max delay, so store the delay and nr */
		if (rs->f_delay > rs->f_max_delay) {
			rs->f_max_delay = rs->f_delay;
			rs->f_max_nr = pinfo->fd->num;
		}
		add_to_clist(TRUE, pinfo->fd->num, pri->info_seq_num, n_time-(rs->f_time),
				 n_jitter, rs->f_seq_num+1 == pri->info_seq_num?TRUE:FALSE, FALSE);

		/* count the cycles */
		if ((pri->info_seq_num < rs->f_start_seq_nr) && (rs->f_under == FALSE)){
			rs->f_cycles++;
			rs->f_under = TRUE;
		}
		else if ((pri->info_seq_num == 0) && (rs->f_stop_seq_nr == 65535) && 
								(rs->f_under == FALSE)){
			rs->f_cycles++;
			rs->f_under = TRUE;
		}
		/* the whole round is over, so reset the flag */
		else if ((pri->info_seq_num>rs->f_start_seq_nr+1)&&(rs->f_under!=FALSE)){
			rs->f_under = FALSE;
		}

		/* number of times where sequence number was not ok */
		if ( rs->f_seq_num+1 == pri->info_seq_num)
			rs->f_seq_num = pri->info_seq_num;
		else if ( (rs->f_seq_num == 65535) && (pri->info_seq_num == 0) )
			rs->f_seq_num = pri->info_seq_num;
		/* lost packets */
		else if (rs->f_seq_num+1 < pri->info_seq_num) {
			rs->f_seq_num = pri->info_seq_num;
			rs->f_sequence++;
		}
		/* late or duplicated */
		else if (rs->f_seq_num+1 > pri->info_seq_num)
			rs->f_sequence++;

		rs->f_stop_seq_nr = pri->info_seq_num;
		rs->f_time = n_time;
		rs->f_jitter = n_jitter;
		rs->f_timestamp = pri->info_timestamp;
		rs->f_total_nr++;

		/* save the voice information */
		/* we do it only in following cases:
		 * - the codecs we support are g.711 alaw in ulaw
		 * - the captured length must equal the packet length
		 * - XXX we don't support it if there are padding bits 
		 */
		/* if there was already an error, we quit */
		if (rs->f_saved == FALSE)
			return 0;
		/* if the captured length and packet length aren't equal, we quit */
		if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
			rs->f_saved = FALSE;
			rs->f_error_type = WRONG_LENGTH;
			return 0;
		}
		/* if padding bit is set, we don't do it yet */
		if (pri->info_padding_set != FALSE) {
			rs->f_saved = FALSE;
			rs->f_error_type = PADDING_SET;
			return 0;
		}
		/* is it the ulaw? */
		if (pri->info_payload_type == 0) {
			/* cfile.pd points at the beggining of the actual packet. We have
			 * to move this pointer at the RTP data. This is the packet length,
			 * minus whole RTP data length (including the RTP header, that is
			 * why we add 12) */
			data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
			for(i=0; i < (pri->info_data_len - 12); i++, data++) {
				tmp = (gint16 )ulaw2linear((unsigned char)*data);
				fwrite(&tmp, 2, 1, rs->f_fp);
				rs->f_count++;
			}
			return 0;				
		}
		/* alaw? */
		else if (pri->info_payload_type == 8) {
			data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
			for(i=0; i < (pri->info_data_len-12 ); i++, data++) {
				tmp = (gint16 )alaw2linear((unsigned char)*data);
				fwrite(&tmp, 2, 1, rs->f_fp);
				rs->f_count++;
			}
			return 0;
		}
		/* unsupported codec or other error */
		else {
			rs->f_saved = FALSE;
			rs->f_error_type = WRONG_CODEC;
			return 0;
		}
	}				

	/* is it the reversed direction? */
	else if (rs->ssrc_reversed == pri->info_sync_src) {
		/* first packet? */
		if (rs->r_first_packet !=FALSE) {
			rs->r_seq_num = pri->info_seq_num;
			rs->r_delay = 0;
			rs->r_jitter = 0;
			rs->r_first_packet = FALSE;
			rs->r_timestamp = pri->info_timestamp;
			rs->r_start_seq_nr = pri->info_seq_num;
			rs->r_stop_seq_nr = pri->info_seq_num;
			rs->r_total_nr++;
			rs->r_time = (double)pinfo->fd->rel_secs + 
						(double) pinfo->fd->rel_usecs/1000000;
			rs->r_start_time = rs->r_time;
			add_to_clist(FALSE, pinfo->fd->num, pri->info_seq_num, 0, 0, TRUE, FALSE);

			/* save it */
			/* if we couldn't open the tmp file for writing, then we set the flag */
			if (rs->r_fp == NULL) {
				rs->r_saved = FALSE;
				return 0;
			}
			/* if the captured length and packet length aren't equal, we quit */
			if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
				rs->r_saved = FALSE;
				rs->r_error_type = WRONG_LENGTH;
				return 0;
			}
			/* if padding bit is set, we don't do it yet */
			if (pri->info_padding_set != FALSE) {
				rs->r_saved = FALSE;
				rs->r_error_type = PADDING_SET;
				return 0;
			}
			/* is it the ulaw? */
			if (pri->info_payload_type == 0) {
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len-12 ); i++, data++) {
					tmp = (gint16 )ulaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->r_fp);
					rs->r_count++;
				}
				rs->r_saved = TRUE;
				return 0;
			}
			/* alaw? */
			else if (pri->info_payload_type == 8) {
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len -12 ); i++, data++) {
					tmp = (gint16 )alaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->r_fp);
					rs->r_count++;
				}
				rs->r_saved = TRUE;
				return 0;
			}
			/* unsupported codec or other error */
			else {
				rs->r_saved = FALSE;
				rs->r_error_type = WRONG_CODEC;
				return 0;
			}
		}
		
		/* packet with mark bit set? */
		if (pri->info_marker_set != FALSE) {
			n_time = (double)pinfo->fd->rel_secs +
					(double) pinfo->fd->rel_usecs/1000000;
			n_jitter = rs->r_jitter + ( fabs (n_time-(rs->r_time) - 
					((double)(pri->info_timestamp)-
					(double)(rs->r_timestamp))/8000) - rs->r_jitter)/16;
			add_to_clist(FALSE, pinfo->fd->num, pri->info_seq_num, n_time-(rs->r_time),
				 n_jitter, rs->r_seq_num+1 == pri->info_seq_num?TRUE:FALSE, TRUE);

			/* count the cycles */
			if ((pri->info_seq_num < rs->r_start_seq_nr) && (rs->r_under == FALSE)){
				rs->r_cycles++;
				rs->r_under = TRUE;
			}
			else if ((pri->info_seq_num == 0) && (rs->r_stop_seq_nr == 65535) && 
									(rs->r_under == FALSE)){
				rs->r_cycles++;
				rs->r_under = TRUE;
			}
			/* the whole round is over, so reset the flag */
			else if ((pri->info_seq_num>rs->r_start_seq_nr+1)&&(rs->r_under!=FALSE)){
				rs->r_under = FALSE;
			}

			/* number of times where sequence number was not ok */
			if ( rs->r_seq_num+1 == pri->info_seq_num)
				rs->r_seq_num = pri->info_seq_num;
			else if ( (rs->r_seq_num == 65535) && (pri->info_seq_num == 0) )
				rs->r_seq_num = pri->info_seq_num;
			/* lost packets */
			else if (rs->r_seq_num+1 < pri->info_seq_num) {
				rs->r_seq_num = pri->info_seq_num;
				rs->r_sequence++;
			}
			/* late or duplicated */
			else if (rs->r_seq_num+1 > pri->info_seq_num)
				rs->r_sequence++;

			rs->r_stop_seq_nr = pri->info_seq_num;
			rs->r_time = n_time;
			rs->r_jitter = n_jitter;
			rs->r_delta_timestamp = pri->info_timestamp - rs->r_timestamp;
			rs->r_timestamp = pri->info_timestamp;
			rs->r_total_nr++;

			/* save the voice information */
			/* if there was already an error, we quit */
			if (rs->r_saved == FALSE)
				return 0;
			/* if the captured length and packet length aren't equal, we quit */
			if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
				rs->r_saved = FALSE;
				rs->r_error_type = WRONG_LENGTH;
				return 0;
			}
			/* if padding bit is set, we don't do it yet */
			if (pri->info_padding_set != FALSE) {
				rs->r_saved = FALSE;
				rs->r_error_type = PADDING_SET;
				return 0;
			}
			/* because the mark bit is set, we have to add some silence in front */
			/* is it the ulaw? */
			if (pri->info_payload_type == 0) {
				/* we insert some silence */
				for(i=0; i<(rs->r_delta_timestamp-pri->info_data_len+12); i++) {
					tmp = (gint16 )ulaw2linear((unsigned char)(0x55));
					fwrite(&tmp, 2, 1, rs->r_fp);
					rs->r_count++;
				}
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len-12 ); i++, data++) {
					tmp = (gint16 )ulaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->r_fp);
					rs->r_count++;
				}
				return 0;
			}
			/* alaw? */
			else if (pri->info_payload_type == 8) {
				for(i=0; i < (rs->r_delta_timestamp-pri->info_data_len+12); i++) {
					tmp = (gint16 )ulaw2linear((unsigned char)(0x55));
					fwrite(&tmp, 2, 1, rs->r_fp);
					rs->r_count++;
				}
				data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
				for(i=0; i < (pri->info_data_len -12 ); i++, data++) {
					tmp = (gint16 )alaw2linear((unsigned char)*data);
					fwrite(&tmp, 2, 1, rs->r_fp);
					rs->r_count++;
				}
				return 0;
			}
			/* unsupported codec or other error */
			else {
				rs->r_saved = FALSE;
				rs->r_error_type = WRONG_CODEC;
				return 0;
			}
			return 0;
		}
		
		/* normal packet in reversed connection */
		n_time = (double)pinfo->fd->rel_secs +
				(double) pinfo->fd->rel_usecs/1000000;
		n_jitter = rs->r_jitter + ( fabs (n_time-(rs->r_time) - 
				((double)(pri->info_timestamp)-
				(double)(rs->r_timestamp))/8000) - rs->r_jitter)/16;
		rs->r_delay =  n_time-(rs->r_time);
		if (rs->r_delay > rs->r_max_delay) {
			rs->r_max_delay = rs->r_delay;
			rs->r_max_nr = pinfo->fd->num;
		}
		add_to_clist(FALSE, pinfo->fd->num, pri->info_seq_num, n_time-(rs->r_time),
				 n_jitter, rs->r_seq_num+1 == pri->info_seq_num?TRUE:FALSE, FALSE);
		/* count the cycles */
		if ((pri->info_seq_num < rs->r_start_seq_nr) && (rs->r_under == FALSE)){
			rs->r_cycles++;
			rs->r_under = TRUE;
		}
		else if ((pri->info_seq_num == 0) && (rs->r_stop_seq_nr == 65535) && 
								(rs->r_under == FALSE)){
			rs->r_cycles++;
			rs->r_under = TRUE;
		}
		/* the whole round is over, so reset the flag */
		else if ((pri->info_seq_num>rs->r_start_seq_nr+1)&&(rs->r_under!=FALSE)){
			rs->r_under = FALSE;
		}

		/* number of times where sequence number was not ok */
		if ( rs->r_seq_num+1 == pri->info_seq_num)
			rs->r_seq_num = pri->info_seq_num;
		else if ( (rs->r_seq_num == 65535) && (pri->info_seq_num == 0) )
			rs->r_seq_num = pri->info_seq_num;
		/* lost packets */
		else if (rs->r_seq_num+1 < pri->info_seq_num) {
			rs->r_seq_num = pri->info_seq_num;
			rs->r_sequence++;
		}
		/* late or duplicated */
		else if (rs->r_seq_num+1 > pri->info_seq_num)
			rs->r_sequence++;

		rs->r_stop_seq_nr = pri->info_seq_num;
		rs->r_time = n_time;
		rs->r_jitter = n_jitter;
		rs->r_timestamp = pri->info_timestamp;
		rs->r_total_nr++;

		/* save the voice information */
		/* if there was already an error, we quit */
		if (rs->r_saved == FALSE)
			return 0;
		/* if the captured length and packet length aren't equal, we quit */
		if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
			rs->r_saved = FALSE;
			rs->r_error_type = WRONG_LENGTH;
			return 0;
		}
		/* if padding bit is set, we don't do it yet */
		if (pri->info_padding_set != FALSE) {
			rs->r_saved = FALSE;
			rs->r_error_type = PADDING_SET;
			return 0;
		}
		/* is it the ulaw? */
		if (pri->info_payload_type == 0) {
			data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
			for(i=0; i < (pri->info_data_len-12 ); i++, data++) {
				tmp = (gint16 )ulaw2linear((unsigned char)*data);
				fwrite(&tmp, 2, 1, rs->r_fp);
				rs->r_count++;
			}
			return 0;
		}
		/* alaw? */
		else if (pri->info_payload_type == 8) {
			data = cfile.pd + (pinfo->fd->pkt_len - pri->info_data_len + 12);
			for(i=0; i < (pri->info_data_len -12 ); i++, data++) {
				tmp = (gint16 )alaw2linear((unsigned char)*data);
				fwrite(&tmp, 2, 1, rs->r_fp);
				rs->r_count++;
			}
			return 0;
		}
		/* unsupported codec or other error */
		else {
			rs->r_saved = FALSE;
			rs->r_error_type = WRONG_CODEC;
			return 0;
		}
	}				

	return 0;
}

/* XXX just copied from gtk/rpc_stat.c */
void protect_thread_critical_region(void);
void unprotect_thread_critical_region(void);


/* here we close the rtp analysis dialog window and remove the tap listener */
static void rtp_destroy_cb(GtkWidget *win _U_, gpointer data _U_)
{
  info_stat *rs=(info_stat *)data;

  protect_thread_critical_region();
  remove_tap_listener(rs);
  unprotect_thread_critical_region();

  /* xxx is this enough? */
  g_free(rs->ssrc_tmp);
  g_free(rs);
 
  if (rs->f_fp != NULL)
	fclose(rs->f_fp);
  if (rs->r_fp != NULL)
	fclose(rs->r_fp);
  remove(f_tempname);
  remove(r_tempname);

  /* is there a save window open */
  if (save_w != NULL)
	gtk_widget_destroy(save_w);

  /* Is there a save voice window open? */
  if (voice_w != NULL)
	gtk_widget_destroy(voice_w);

  /* Note that we no longer have a "RTP Analyse" dialog box. */
  rtp_w = NULL;
}

/* when the close button in rtp window was clicked */
/* it seems to me that rtp_destroy_cb is automatically called, so we don't
 * need to do the g_free... and rtp_w = NULL ... */
static void rtp_destroy (GtkWidget *close_bt _U_, gpointer parent_w)
{
    gtk_grab_remove(GTK_WIDGET(parent_w));
    gtk_widget_destroy(GTK_WIDGET(parent_w));
}

/* we search the rtp.ssrc node here (thanks to Guy Harris - code here is magic for me */
static guint32 process_node(proto_item *ptree_node, header_field_info *hfinformation) 
{
  field_info            *finfo;
  proto_item            *proto_sibling_node;
  header_field_info     *hfssrc;
  guint32 ssrc;

  finfo = PITEM_FINFO(ptree_node);

  if (hfinformation==(finfo->hfinfo)) {
	hfssrc = proto_registrar_get_byname("rtp.ssrc");
	if (hfssrc == NULL)
		return 0;
	for(ptree_node=g_node_first_child(ptree_node); ptree_node!=NULL; 
				ptree_node=g_node_next_sibling(ptree_node)) {
		finfo=PITEM_FINFO(ptree_node);
		if (hfssrc==finfo->hfinfo) {
			ssrc = fvalue_get_integer(finfo->value);
			return ssrc;
		}
		}
  }

  proto_sibling_node = g_node_next_sibling(ptree_node);

  if (proto_sibling_node) {
	ssrc = process_node(proto_sibling_node, hfinformation);
	return ssrc;
  }
  else
	return 0;
}

/* here we search the rtp protocol */
static guint32 process_tree(proto_tree *protocol_tree)
{
  proto_item      *ptree_node;
  header_field_info     *hfinformation;

  hfinformation = proto_registrar_get_byname("rtp");
  if (hfinformation == NULL)
	return 0;

  ptree_node = g_node_first_child(protocol_tree);
  if (!ptree_node)
	return 0;

  return process_node(ptree_node, hfinformation);
}

/* when we want to update the information */
static void refresh_cb(GtkWidget *w _U_, void *pri)
{
  info_stat *rs=pri;

  gtk_clist_clear(GTK_CLIST(clist));
  gtk_clist_clear(GTK_CLIST(clist_r));
  redissect_packets(&cfile);
  draw_stat(rs);
}

/* when the user clicks the close button */
static void voice_close_cb(GtkWidget *close_bt _U_, gpointer parent_w _U_)
{
  gtk_widget_destroy(GTK_WIDGET(parent_w));
}

static void voice_destroy_cb(GtkWidget *win _U_, gpointer user_data _U_)
{
  if (save_w != NULL)
  	gtk_widget_destroy(GTK_WIDGET(save_w));

  /* Note that we no longer have a Save voice info dialog box. */
  voice_w = NULL;
}


/* the user wants to save in a file */
/* XXX support for different formats is currently commented out */
static void ok_button_cb(GtkWidget *ok_bt, gpointer data)
{
  info_stat *rs=(info_stat *)data;
  GtkWidget *entry, *rev, *forw, *both;
  /*GtkWidget *wav, *au, *sw;*/
  gchar *g_dest;
  gint channels /*, format*/;

  entry = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "file_entry");
  /*wav = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "wav_rb");
  au = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "au_rb");
  sw = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "sw_rb");*/
  rev = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "reversed_rb");
  forw = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "forward_rb");
  both = (GtkWidget *)OBJECT_GET_DATA(ok_bt, "both_rb");
  g_dest = gtk_entry_get_text(GTK_ENTRY(entry));

  /* XXX user clicks the ok button, but we know we can't save the voice info because f.e.
   * we don't support that codec. So we pop up a warning. Maybe it would be better to
   * disable the ok button or disable the buttons for direction if only one is not ok. The
   * problem is if we open the save voice dialog and then click the refresh button and maybe 
   * the state changes, so we can't save anymore. In this case we should be able to update
   * the buttons. For now it is easier if we put the warning when the ok button is pressed.
   */

  /* we can not save in both dirctions */
  if ((rs->f_saved == FALSE) && (rs->r_saved == FALSE) && (GTK_TOGGLE_BUTTON (both)->active)) {
	/* there are many combinations here, we just exit when first matches */
	if ((rs->f_error_type == WRONG_CODEC) || (rs->r_error_type == WRONG_CODEC))
		simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save in a file: Unsupported codec!");
	else if ((rs->f_error_type == WRONG_LENGTH) || (rs->r_error_type == WRONG_LENGTH))
		simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save in a file: Wrong length of captured packets!");
	else if ((rs->f_error_type == PADDING_SET) || (rs->r_error_type == PADDING_SET))
		simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save in a file: RTP data with padding!");
	else  
		simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save in a file: File I/O problem!");
	return;
  }
  /* we can not save forward direction */
  else if ((rs->f_saved == FALSE) && ((GTK_TOGGLE_BUTTON (forw)->active) ||
						(GTK_TOGGLE_BUTTON (both)->active))) {  
	if (rs->f_error_type == WRONG_CODEC)
                simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save forward direction in a file: Unsupported codec!");
        else if (rs->f_error_type == WRONG_LENGTH)
                simple_dialog(ESD_TYPE_CRIT, NULL,
                "Can't save forward direction in a file: Wrong length of captured packets!");
        else if (rs->f_error_type == PADDING_SET)
                simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save forward direction in a file: RTP data with padding!");
        else
                simple_dialog(ESD_TYPE_CRIT, NULL, 
		"Can't save forward direction in a file: File I/O problem!");
	return;
  }
  /* we can not save reversed direction */
  else if ((rs->r_saved == FALSE) && ((GTK_TOGGLE_BUTTON (rev)->active) ||
						(GTK_TOGGLE_BUTTON (both)->active))) {  
	if (rs->r_error_type == WRONG_CODEC)
                simple_dialog(ESD_TYPE_CRIT, NULL,
                "Can't save reversed direction in a file: Unsupported codec!");
        else if (rs->r_error_type == WRONG_LENGTH)
                simple_dialog(ESD_TYPE_CRIT, NULL,
                "Can't save reversed direction in a file: Wrong length of captured packets!");
        else if (rs->r_error_type == PADDING_SET)
                simple_dialog(ESD_TYPE_CRIT, NULL,
                "Can't save reversed direction in a file: RTP data with padding!");
        else if (rs->r_error_type == NO_DATA)
                simple_dialog(ESD_TYPE_CRIT, NULL,
                "Can't save reversed direction in a file: No RTP data!");
	else
                simple_dialog(ESD_TYPE_CRIT, NULL,
                "Can't save reversed direction in a file: File I/O problem!");
        return;
  }

  /* if the file text entry is empty */
  if (!g_dest[0]) {
	simple_dialog(ESD_TYPE_CRIT, NULL, "Saving to file, but no file specified.");
	return;
  }

  /*if (GTK_TOGGLE_BUTTON (wav)->active)
	format = 1;
  else if (GTK_TOGGLE_BUTTON (au)->active)
	format = 2;
  else if (GTK_TOGGLE_BUTTON (sw)->active)
	format = 3;*/

  if (GTK_TOGGLE_BUTTON (rev)->active)
	channels = 2;
  else if (GTK_TOGGLE_BUTTON (both)->active)
	channels = 3;
  else 
	channels = 1;

  if(!copy_file(g_dest, channels/*, format*/, rs)) {
	simple_dialog(ESD_TYPE_CRIT, NULL, "An error occured while saving voice in a file!");
	return;
  }

  if (save_w != NULL)
  	gtk_widget_destroy(GTK_WIDGET(save_w));

  /* XXX I get GTK warning (sometimes?)!!! */
  gtk_widget_destroy(GTK_WIDGET(voice_w));
}

static void save_ok_button_cb(GtkWidget *w _U_, gpointer data)
{
  gchar     *f_name;

  f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));

  /* Perhaps the user specified a directory instead of a file.
     Check whether they did. */
  if (test_for_directory(f_name) == EISDIR) {
	/* It's a directory - set the file selection box to display it. */
	set_last_open_dir(f_name);
	g_free(f_name);
	gtk_file_selection_set_filename(GTK_FILE_SELECTION(data), last_open_dir);
	return;
  }

  gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(data, "file_entry")), f_name);
  gtk_widget_destroy(GTK_WIDGET(data));

  g_free(f_name);
}

static void save_cancel_button_cb(GtkWidget *w _U_, gpointer data)
{
  gtk_widget_destroy(GTK_WIDGET(data));
}

static void save_destroy_cb(GtkWidget *win _U_, gpointer user_data _U_)
{
  /* Note that we no longer have a Save voice info dialog box. */
  save_w = NULL;
}

/* file selection window */
static void file_selection_cb(GtkWidget *w _U_, gpointer file_entry)
{
 if (save_w != NULL) {
	/* Yes.  Just re-activate that dialog box. */
	reactivate_window(save_w);
	return;
  }

  save_w = gtk_file_selection_new ("Ethereal: Save to File");
  gtk_signal_connect(GTK_OBJECT(save_w), "destroy", GTK_SIGNAL_FUNC(save_destroy_cb), NULL);

  OBJECT_SET_DATA(save_w, "file_entry", file_entry);

  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(save_w)->cancel_button), "clicked", 
		GTK_SIGNAL_FUNC(save_cancel_button_cb), GTK_OBJECT(save_w));
  
  gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(save_w)->ok_button), "clicked",
		GTK_SIGNAL_FUNC(save_ok_button_cb), GTK_OBJECT(save_w));
  
  gtk_widget_show(save_w);
}

/* when the user wants to save the voice information in a file */
/* XXX support for different formats is currently commented out */
static void voice_cb(GtkWidget *w _U_, gpointer data)
{
  info_stat *rs=(info_stat *)data;

  GtkWidget *vertb;
  GtkWidget *table1;
  GtkWidget *label_format;
  GtkWidget *channels_label;
  /*GSList *format_group = NULL;*/
  GSList *channels_group = NULL;
  GtkWidget *forward_rb;
  GtkWidget *reversed_rb;
  GtkWidget *both_rb;
  /*GtkWidget *wav_rb; GtkWidget *au_rb; GtkWidget *sw_rb;*/
  GtkWidget *hbox3;
  GtkWidget *file_button;
  GtkWidget *file_entry;
  GtkWidget *hbox4;
  GtkWidget *ok_bt;
  GtkWidget *cancel_bt;

  /* if we can't save in a file: wrong codec, cut packets or other errors */
  /* shold the error arise here or later when you click ok button ? 
   * if we do it here, then we must disable the refresh button, so we don't do it here */

  if (voice_w != NULL) {
	/* There's already a Save voice info dialog box; reactivate it. */
	reactivate_window(voice_w);
	return;
  }
  
  voice_w = dlg_window_new("Ethereal: Save voice data");
  gtk_signal_connect(GTK_OBJECT(voice_w), "destroy",
       GTK_SIGNAL_FUNC(voice_destroy_cb), NULL);

  /* Container for each row of widgets */
  vertb = gtk_vbox_new(FALSE, 0);
  gtk_container_border_width(GTK_CONTAINER(vertb), 5);
  gtk_container_add(GTK_CONTAINER(voice_w), vertb);
  gtk_widget_show (vertb);

  table1 = gtk_table_new (2, 4, FALSE);
  gtk_widget_show (table1);
  gtk_box_pack_start (GTK_BOX (vertb), table1, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (table1), 10);
  gtk_table_set_row_spacings (GTK_TABLE (table1), 20);

  label_format = gtk_label_new ("Format: .au (ulaw, 8 bit, 8000 Hz, mono) ");
  gtk_widget_show (label_format);
  gtk_table_attach (GTK_TABLE (table1), label_format, 0, 3, 0, 1,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);

  /* we support .au - ulaw*/ 
/*  wav_rb = gtk_radio_button_new_with_label (format_group, ".wav");
  format_group = gtk_radio_button_group (GTK_RADIO_BUTTON (wav_rb));
  gtk_widget_show (wav_rb);
  gtk_table_attach (GTK_TABLE (table1), wav_rb, 1, 2, 0, 1,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);

  sw_rb = gtk_radio_button_new_with_label (format_group, "8 kHz, 16 bit  ");
  format_group = gtk_radio_button_group (GTK_RADIO_BUTTON (sw_rb));
  gtk_widget_show (sw_rb);
  gtk_table_attach (GTK_TABLE (table1), sw_rb, 2, 3, 0, 1,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);
  au_rb = gtk_radio_button_new_with_label (format_group, ".au");
  format_group = gtk_radio_button_group (GTK_RADIO_BUTTON (au_rb));
  gtk_widget_show (au_rb);
  gtk_table_attach (GTK_TABLE (table1), au_rb, 3, 4, 0, 1,
                    (GtkAttachOptions) (GTK_FILL),
                    (GtkAttachOptions) (0), 0, 0);
 */ 

  channels_label = gtk_label_new ("Channels:");
  gtk_widget_show (channels_label);
  gtk_table_attach (GTK_TABLE (table1), channels_label, 0, 1, 1, 2,
				(GtkAttachOptions) (GTK_FILL),
				(GtkAttachOptions) (0), 0, 0);
  gtk_misc_set_alignment (GTK_MISC (channels_label), 0, 0.5);

  forward_rb = gtk_radio_button_new_with_label (channels_group, "forward  ");
  channels_group = gtk_radio_button_group (GTK_RADIO_BUTTON (forward_rb));
  gtk_widget_show (forward_rb);
  gtk_table_attach (GTK_TABLE (table1), forward_rb, 1, 2, 1, 2,
			(GtkAttachOptions) (GTK_FILL),
			(GtkAttachOptions) (0), 0, 0);

  reversed_rb = gtk_radio_button_new_with_label (channels_group, "reversed");
  channels_group = gtk_radio_button_group (GTK_RADIO_BUTTON (reversed_rb));
  gtk_widget_show (reversed_rb);
  gtk_table_attach (GTK_TABLE (table1), reversed_rb, 2, 3, 1, 2,
			(GtkAttachOptions) (GTK_FILL),
			(GtkAttachOptions) (0), 0, 0);

  both_rb = gtk_radio_button_new_with_label (channels_group, "both");
  channels_group = gtk_radio_button_group (GTK_RADIO_BUTTON (both_rb));
  gtk_widget_show (both_rb);
  gtk_table_attach (GTK_TABLE (table1), both_rb, 3, 4, 1, 2,
			(GtkAttachOptions) (GTK_FILL),
			(GtkAttachOptions) (0), 0, 0);

  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(both_rb), TRUE);

  /* if one direction is nok we don't allow saving 
  XXX this is not ok since the user can click the refresh button and cause changes
  but we can not update this window. So we move all the decision on the time the ok
  button is clicked
  if (rs->f_saved == FALSE) {
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(reversed_rb), TRUE);
	gtk_widget_set_sensitive(forward_rb, FALSE);
	gtk_widget_set_sensitive(both_rb, FALSE);
  }
  else if (rs->r_saved == FALSE) {
	gtk_widget_set_sensitive(reversed_rb, FALSE);
	gtk_widget_set_sensitive(both_rb, FALSE);
  }
  */
  hbox3 = gtk_hbox_new (FALSE, 0);
  gtk_widget_show (hbox3);
  gtk_box_pack_start (GTK_BOX (vertb), hbox3, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox3), 10);

  file_button = gtk_button_new_with_label ("File:");
  gtk_widget_show (file_button);
  gtk_box_pack_start (GTK_BOX (hbox3), file_button, FALSE, FALSE, 0);

  file_entry = gtk_entry_new ();
  gtk_widget_show (file_entry);
  gtk_box_pack_start (GTK_BOX (hbox3), file_entry, TRUE, TRUE, 10);

  hbox4 = gtk_hbox_new (FALSE, 0);
  gtk_widget_show (hbox4);
  gtk_box_pack_start (GTK_BOX (vertb), hbox4, FALSE, FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (hbox4), 10);

  ok_bt = gtk_button_new_with_label ("OK");
  gtk_widget_show (ok_bt);
  gtk_box_pack_start (GTK_BOX (hbox4), ok_bt, TRUE, FALSE, 0);
  gtk_widget_set_usize (ok_bt, 60, -2);
  OBJECT_SET_DATA(ok_bt, "file_entry", file_entry);
  /*OBJECT_SET_DATA(ok_bt, "wav_rb", wav_rb);
  OBJECT_SET_DATA(ok_bt, "au_rb", au_rb);
  OBJECT_SET_DATA(ok_bt, "sw_rb", sw_rb);*/
  OBJECT_SET_DATA(ok_bt, "forward_rb", forward_rb);
  OBJECT_SET_DATA(ok_bt, "reversed_rb", reversed_rb);
  OBJECT_SET_DATA(ok_bt, "both_rb", both_rb);

  cancel_bt = gtk_button_new_with_label (" Cancel ");
  gtk_widget_show (cancel_bt);
  gtk_box_pack_start (GTK_BOX (hbox4), cancel_bt, TRUE, FALSE, 0);
  gtk_widget_set_usize (cancel_bt, 60, -2);
  
  gtk_signal_connect(GTK_OBJECT(cancel_bt), "clicked",
		GTK_SIGNAL_FUNC(voice_close_cb), GTK_OBJECT(voice_w));
  
  gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
		GTK_SIGNAL_FUNC(ok_button_cb), rs);
  
  gtk_signal_connect(GTK_OBJECT(file_button), "clicked",
		GTK_SIGNAL_FUNC(file_selection_cb), file_entry);
  
  gtk_widget_show(voice_w);
}

/* all the graphics on the window is done here */
static void add_rtp_notebook(void *pri) 
{
  info_stat *rs=pri;

  GtkWidget *notebook, *page, *page_r, *label, *label1, *label2, *label3;
  GtkWidget *scrolled_window, *scrolled_window_r/*, *frame, *text, *label4, *page_help*/;
  GtkWidget *box4, *voice_bt, *refresh_bt, *close_bn;
  
  gchar *titles[6] =  {"Packet nr.", "Sequence",  "Delay (s)", "Jitter (s)", "Marker", "Status"};
  gchar label_forward[150];
  gchar label_reverse[150];

  /* XXX is it ok to use %lu for guint32? The compiler is not satisfied, but it works
   * with %d the compiler is satisfied, but it doesn't work */
  g_snprintf(label_forward, 149, 
		"Analysing connection from  %s port %lu  to  %s port %lu   SSRC = %lu\n", 
		rs->source, rs->srcport, rs->destination, rs->dstport, rs->ssrc_forward);
  g_snprintf(label_reverse, 149,
		"Analysing connection from  %s port %lu  to  %s port %lu   SSRC = %lu\n", 
		rs->destination, rs->dstport, rs->source, rs->srcport, rs->ssrc_reversed);

  gtk_widget_destroy(main_vb);
  main_vb = gtk_vbox_new(FALSE, 3);
  gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
  gtk_container_add(GTK_CONTAINER(rtp_w), main_vb);
  gtk_widget_show(main_vb);

  /* Start a nootbook for flipping between sets of changes */
  notebook = gtk_notebook_new();
  gtk_container_add(GTK_CONTAINER(main_vb), notebook);
  gtk_object_set_data(GTK_OBJECT(rtp_w), "notebook", notebook);

  /* page for forward connection */
  page = gtk_vbox_new(FALSE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(page), 20);

  /* scrolled window */
  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_widget_set_usize(scrolled_window, 600, 200);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), 
					GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);

  /* direction label */
  label1 = gtk_label_new(label_forward);
  gtk_box_pack_start(GTK_BOX(page), label1, FALSE, FALSE, 0);

  /* place for some statistics */
  max = gtk_label_new("\n\n");
  gtk_box_pack_end(GTK_BOX(page), max, FALSE, FALSE, 5);

  /* clist for the information */
  clist = gtk_clist_new_with_titles(6, titles);
  gtk_widget_show(clist);
  gtk_container_add(GTK_CONTAINER(scrolled_window), clist);
  gtk_box_pack_start(GTK_BOX(page), scrolled_window, TRUE, TRUE, 0);

  /* and the label */
  label = gtk_label_new("     Forward Direction     ");
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);

  /* column width and justification */
  gtk_clist_set_column_width(GTK_CLIST(clist), 0, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist), 1, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist), 2, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist), 3, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist), 4, 40);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 0, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 1, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 2, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 3, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 4, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist), 5, GTK_JUSTIFY_CENTER);

  /* same page for reversed connection */
  page_r = gtk_vbox_new(FALSE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(page_r), 20);
  scrolled_window_r = gtk_scrolled_window_new(NULL, NULL);
  gtk_widget_set_usize(scrolled_window_r, 600, 200);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window_r), 
				GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
  label3 = gtk_label_new(label_reverse);
  gtk_box_pack_start(GTK_BOX(page_r), label3, FALSE, FALSE, 0);
  max_r = gtk_label_new("\n\n");
  gtk_box_pack_end(GTK_BOX(page_r), max_r, FALSE, FALSE, 5);
  clist_r = gtk_clist_new_with_titles(6, titles);
  gtk_widget_show(clist_r);
  gtk_container_add(GTK_CONTAINER(scrolled_window_r), clist_r);
  gtk_box_pack_start(GTK_BOX(page_r), scrolled_window_r, TRUE, TRUE, 0);
  label2 = gtk_label_new("     Reversed Direction     ");
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page_r, label2);

  gtk_clist_set_column_width(GTK_CLIST(clist_r), 0, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist_r), 1, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist_r), 2, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist_r), 3, 80);
  gtk_clist_set_column_width(GTK_CLIST(clist_r), 4, 40);
  gtk_clist_set_column_justification(GTK_CLIST(clist_r), 0, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist_r), 1, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist_r), 2, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist_r), 3, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist_r), 4, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_justification(GTK_CLIST(clist_r), 5, GTK_JUSTIFY_CENTER);

  /* page for help&about or future 
  page_help = gtk_hbox_new(FALSE, 5);
  label4 = gtk_label_new("     Future    ");
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page_help, label4);
  frame = gtk_frame_new("");
  text = gtk_label_new("\n\nMaybe some more statistics: delay and jitter distribution,...");
  gtk_label_set_justify(GTK_LABEL(text), GTK_JUSTIFY_LEFT);
  gtk_container_add(GTK_CONTAINER(frame), text);
  gtk_container_set_border_width(GTK_CONTAINER(frame), 20);
  gtk_box_pack_start(GTK_BOX(page_help), frame, TRUE, TRUE, 0);
*/
  /* show all notebooks */
  gtk_widget_show_all(notebook);

  /* and the buttons */
  box4 = gtk_hbutton_box_new();
  gtk_box_pack_start(GTK_BOX(main_vb), box4, FALSE, TRUE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(box4), 10);
  gtk_button_box_set_layout(GTK_BUTTON_BOX(box4), GTK_BUTTONBOX_SPREAD);
  gtk_widget_show(box4);

  voice_bt = gtk_button_new_with_label("Save voice data");
  gtk_container_add(GTK_CONTAINER(box4), voice_bt);
  gtk_widget_show(voice_bt);
  gtk_signal_connect(GTK_OBJECT(voice_bt), "clicked",
		GTK_SIGNAL_FUNC(voice_cb), rs);

  refresh_bt = gtk_button_new_with_label("Refresh");
  gtk_container_add(GTK_CONTAINER(box4), refresh_bt);
  gtk_widget_show(refresh_bt);
  gtk_signal_connect(GTK_OBJECT(refresh_bt), "clicked",
		GTK_SIGNAL_FUNC(refresh_cb), rs);

  close_bn = gtk_button_new_with_label("Close");
  gtk_container_add(GTK_CONTAINER(box4), close_bn);
  gtk_widget_show(close_bn);
  gtk_signal_connect(GTK_OBJECT(close_bn), "clicked",
		GTK_SIGNAL_FUNC(rtp_destroy), GTK_OBJECT(rtp_w));

  redissect_packets(&cfile);

  draw_stat(rs);
}


/* when we click on the selected row it copies that ssrc value into ssrc_reversed */
static void get_selected_ssrc(GtkWidget *clist_r, gint row, gint column, 
						GdkEventButton *event _U_, gpointer data)
{
  info_stat *rs=(info_stat *)data;
  gchar *text;

  gtk_clist_get_text(GTK_CLIST(clist_r), row, column, &text);
  /* XXX is this strtoul portable for guint32? */
  rs->ssrc_reversed = strtoul(text, (char **)NULL, 10);
  return;
}

/* when we click apply button in ssrc reversed dialog */
static void apply_selected_ssrc(GtkWidget *w _U_, gpointer data)
{
  info_stat *rs=(info_stat *)data;
  add_rtp_notebook(rs);
}

/* this function goes through all the packets that have the same ip and port combination 
 * (only inversed) as the forward direction (XXX what if the reversed direction doesn't use 
 * the same ports???) and looks for different SSRC values. This can happen if you capture
 * two RTP conversations one after another from the same pair of phones (PC's). 
 * Both have same IP's and can also have same port numbers, so they (should) differ only 
 * in SSRC values. In such case we get a list of ssrc values and we have to choose the right 
 * one from the list. If there is only one or none, we do it automatically */ 
static void get_reversed_ssrc(void *prs)
{
	info_stat *ri = prs;
	GtkWidget *scroll_r, *clist_r, *ok_bt, *label, *label2, *label1, *main_hbnbox;
	gchar temp[150];
	guint i;

	switch(ri->reversed_ip_and_port)
	{
		/* in case we haven't found any reversed ssrc */
		/* XXX in this case we could look for the inversed IP only */
		case 0: {
			ri->ssrc_reversed = 0;
			ri->search_ssrc = FALSE;
			add_rtp_notebook(ri);
			return;
		}
		/* in case we found exactly one matching ssrc for reversed connection */ 
		case 1: { 
			ri->ssrc_reversed = ri->ssrc_tmp[0];
			ri->search_ssrc = FALSE;
			add_rtp_notebook(ri);
			return;
		}
		/* there is more then one matching ssrc, so we have to choose between them */
		default: {
			ri->search_ssrc = FALSE;
			/* let's draw the window */
			label = gtk_label_new("Found more SSRC values for the reversed\n"
						 "connection with following parameters:\n");
			g_snprintf(temp, 149, "Source %s port %lu Destination %s port %lu", 
					ri->destination, ri->dstport, ri->source, ri->srcport);
			label2 = gtk_label_new(temp);
			gtk_box_pack_start(GTK_BOX(main_vb), label, FALSE, FALSE, 0);
			gtk_box_pack_start(GTK_BOX(main_vb), label2, FALSE, FALSE, 0);
			scroll_r = gtk_scrolled_window_new(NULL, NULL);
			gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_r), 
						GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
			clist_r = gtk_clist_new(1);
			gtk_clist_set_column_width(GTK_CLIST(clist_r), 0, 80);
			gtk_container_add(GTK_CONTAINER(scroll_r), clist_r);
			gtk_box_pack_start(GTK_BOX(main_vb), scroll_r, TRUE, TRUE, 0);
			label1 = gtk_label_new("Select one value and click apply");
			gtk_box_pack_start(GTK_BOX(main_vb), label1, FALSE, FALSE, 0);

			main_hbnbox = gtk_hbutton_box_new();
			gtk_box_pack_start(GTK_BOX(main_vb), main_hbnbox, FALSE, TRUE, 0);
			gtk_container_set_border_width(GTK_CONTAINER(main_hbnbox), 10);
			gtk_button_box_set_layout(GTK_BUTTON_BOX(main_hbnbox), 
								GTK_BUTTONBOX_SPREAD);
			gtk_widget_show(main_hbnbox);

			ok_bt = gtk_button_new_with_label("Apply");
			gtk_container_add(GTK_CONTAINER(main_hbnbox), ok_bt);
			gtk_signal_connect(GTK_OBJECT(clist_r), "select_row", 
					GTK_SIGNAL_FUNC(get_selected_ssrc), ri);
			gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked", 
					GTK_SIGNAL_FUNC(apply_selected_ssrc), ri);

			/* add all the ssrc values in the clist */
			/* XXX I'm sure the tmp variable could be avoided here
			 * i tried to assign guint32 from ri->ssrc_tmp somehow to gchar **text
			 * but gave up. So if you can do this, just go ahead */
			for (i=0; i < ri->reversed_ip_and_port; i++) {
				gchar *text[0];
				gchar tmp[20];
				g_snprintf(tmp, 20, "%lu", ri->ssrc_tmp[i]);
				text[0] = (gchar *)&tmp;
				gtk_clist_append(GTK_CLIST(clist_r), text);
			}
			
			gtk_clist_select_row(GTK_CLIST(clist_r), 0, 0);

			gtk_widget_show(label);
			gtk_widget_show(label1);
			gtk_widget_show(label2);
			gtk_widget_show(ok_bt);
			gtk_widget_show(clist_r);
			gtk_widget_show(scroll_r);
		}
	}
}

/* when the user clicks the RTP dialog button */
void rtp_analyse_cb(GtkWidget *w _U_, gpointer data _U_) 
{ 
  info_stat *rs;
  gchar filter_text[]="rtp";
  dfilter_t *sfcode;
  capture_file *cf;
  epan_dissect_t *edt;
  gint err;
  gboolean frame_matched;
  frame_data *fdata;

  /* There's already a "Display Options" dialog box; reactivate it. */
  if (rtp_w != NULL) {
	reactivate_window(rtp_w);
	return;
  }

  /* Try to compile the filter. */
  if (!dfilter_compile(filter_text, &sfcode)) {
	simple_dialog(ESD_TYPE_CRIT, NULL, dfilter_error_msg);
	return;
  }
  /* we load the current file into cf variable */
  cf = &cfile;
  fdata = cf->current_frame;

  /* we are on the selected frame now */
  if (fdata == NULL)
	return; /* if we exit here it's an error */

  /* XXX instead of looking for RTP protocol like this, we could do the process_node() staff */
  /* dissect the current frame */
  wtap_seek_read(cf->wth, fdata->file_off, &cf->pseudo_header, cf->pd, fdata->cap_len, &err);
  edt = epan_dissect_new(TRUE, FALSE);
  epan_dissect_prime_dfilter(edt, sfcode);
  epan_dissect_run(edt, &cf->pseudo_header, cf->pd, fdata, &cf->cinfo);
  frame_matched = dfilter_apply_edt(sfcode, edt);

  /* if it is not an rtp frame, exit */
  frame_matched = dfilter_apply_edt(sfcode, edt);
  if (frame_matched != 1) {
	epan_dissect_free(edt);
	simple_dialog(ESD_TYPE_CRIT, NULL, "You didn't choose a RTP packet!");
	return;
	}

  /* in rs we put all the info */
  rs=g_malloc(sizeof(info_stat));	

  /* ok, it is a RTP frame, so let's get the ip and port values */
  rs->srcport = edt->pi.srcport;
  rs->dstport = edt->pi.destport;
  strncpy(rs->source, ip_to_str(edt->pi.src.data), 16);  
  strncpy(rs->destination, ip_to_str(edt->pi.dst.data), 16);  

  /* now we need the SSRC value of the current frame */
  rs->ssrc_forward = process_tree(edt->tree);
  if (rs->ssrc_forward == 0) {
	simple_dialog(ESD_TYPE_CRIT, NULL, "SSRC value couldn't be found!");
	return;
  }

  /* now we have all the information about the forwarding connection
   * we need to go through all the packets and search for reversed connection
   */
  rs->search_ssrc = TRUE;
  rs->ssrc_reversed = 0;	
  rs->reversed_ip = 0;
  rs->reversed_ip_and_port = 0;
  rs->ssrc_tmp = NULL;

/* XXX compiler warning:passing arg 5 of `register_tap_listener' from incompatible pointer type */
  if(register_tap_listener("rtp", rs, NULL, rtp_reset, rtp_packet, rtp_draw)){
	printf("ethereal: rtp_init() failed to attach the tap.\n");
	/* XXX is this enough or do I have to free anything else? */
	g_free(rs);
	exit(1);
  }

  /* let's draw the window */
  rtp_w = dlg_window_new("Ethereal: RTP Analyse");
  gtk_window_set_position (GTK_WINDOW (rtp_w), GTK_WIN_POS_CENTER);
  gtk_signal_connect(GTK_OBJECT(rtp_w), "destroy",
	GTK_SIGNAL_FUNC(rtp_destroy_cb), rs);

  /* Container for each row of widgets */
  main_vb = gtk_vbox_new(FALSE, 3);
  gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
  gtk_container_add(GTK_CONTAINER(rtp_w), main_vb);
  gtk_widget_show(main_vb);

  /* file names for storing sound data */
  tmpnam(f_tempname);
  tmpnam(r_tempname);
  rs->f_fp = NULL;
  rs->r_fp = NULL;

  redissect_packets(cf);

  /* so how many reversed connection we have ? */
  get_reversed_ssrc(rs);

  /* and finally display this window */
  gtk_widget_show(rtp_w);
}

/* XXX compiler warning:passing arg 2 of `register_ethereal_tap' from incompatible pointer type */
void
register_tap_listener_gtkrtp(void)
{
	register_ethereal_tap("rtp", rtp_analyse_cb, NULL, NULL);
}


/* here we save it into a file that user specified */
/* XXX what about endians here? could go something wrong? */
static gboolean copy_file(gchar *dest, gint channels, /*gint format,*/ void *data)
{
	info_stat *rs=(info_stat *)data;
	int to_fd, forw_fd, rev_fd, fread = 0, rread = 0, fwritten, rwritten;
	gint16 f_pd;
	gint16 r_pd;
	gchar pd[1];
	guint32 f_write_silence = 0;
	guint32 r_write_silence = 0;
	progdlg_t *progbar;
	guint32 progbar_count, progbar_quantum, progbar_nextstep = 0, count = 0;
	gboolean stop_flag = FALSE;

	forw_fd = open(f_tempname, O_RDONLY | 0);
	if (forw_fd < 0) 
		return FALSE;
	rev_fd = open(r_tempname, O_RDONLY | 0);
	if (rev_fd < 0) {
		close(forw_fd); 
		return FALSE;
	}

	/* open file for saving */
	to_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC | 0, 0644);
	if (to_fd < 0) {
		close(forw_fd);
		close(rev_fd);
		return FALSE;
	}

	progbar = create_progress_dlg("Saving voice in a file", dest, "Stop", &stop_flag);

	/* First we write the .au header. XXX Hope this is endian independant */
	/* the magic word 0x2e736e64 == .snd */
	*pd = (unsigned char)0x2e; write(to_fd, pd, 1);
	*pd = (unsigned char)0x73; write(to_fd, pd, 1);
	*pd = (unsigned char)0x6e; write(to_fd, pd, 1);
	*pd = (unsigned char)0x64; write(to_fd, pd, 1);
	/* header offset == 24 bytes */
	*pd = (unsigned char)0x00; write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	*pd = (unsigned char)0x18; write(to_fd, pd, 1);
	/* total length, it is permited to set this to 0xffffffff */
	*pd = (unsigned char)0xff; write(to_fd, pd, 1); 
	write(to_fd, pd, 1); 
	write(to_fd, pd, 1); 
	write(to_fd, pd, 1);
	/* encoding format == 8 bit ulaw */
	*pd = (unsigned char)0x00; write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	*pd = (unsigned char)0x01; write(to_fd, pd, 1);
	/* sample rate == 8000 Hz */
	*pd = (unsigned char)0x00; write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	*pd = (unsigned char)0x1f; write(to_fd, pd, 1);
	*pd = (unsigned char)0x40; write(to_fd, pd, 1);
	/* channels == 1 */
	*pd = (unsigned char)0x00; write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	write(to_fd, pd, 1);
	*pd = (unsigned char)0x01; write(to_fd, pd, 1);
	
	switch (channels) {
		/* only forward direction */
		case 1: {
			progbar_count = rs->f_count;
			progbar_quantum = rs->f_count/100;
			while ((fread = read(forw_fd, &f_pd, 2)) > 0) {
				if(stop_flag) 
					break;
				if((count > progbar_nextstep) && (count <= progbar_count)) {
					update_progress_dlg(progbar, 
						(gfloat) count/progbar_count, "Saving");
					progbar_nextstep = progbar_nextstep + progbar_quantum;
				}
				count++;
				*pd = (unsigned char)linear2ulaw(f_pd);
				fwritten = write(to_fd, pd, 1);
				if ((fwritten*2 < fread) || (fwritten < 0) || (fread < 0)) {
					close(forw_fd);
					close(rev_fd);
					close(to_fd);
					destroy_progress_dlg(progbar);
					return FALSE;
				}
			}
			break;
		}
		/* only reversed direction */
		case 2: {
			progbar_count = rs->r_count;
			progbar_quantum = rs->r_count/100;
			while ((rread = read(rev_fd, &r_pd, 2)) > 0) {
				if(stop_flag) 
					break;
				if((count > progbar_nextstep) && (count <= progbar_count)) {
					update_progress_dlg(progbar, 
						(gfloat) count/progbar_count, "Saving");
					progbar_nextstep = progbar_nextstep + progbar_quantum;
				}
				count++;
				*pd = (unsigned char)linear2ulaw(r_pd);
				rwritten = write(to_fd, pd, 1);
				if ((rwritten*2 < rread) || (rwritten < 0) || (rread < 0)) {
					close(forw_fd);
					close(rev_fd);
					close(to_fd);
					destroy_progress_dlg(progbar);
					return FALSE;
				}
			}
			break;
		}
		/* both directions */
		default: {
			(rs->f_count > rs->r_count) ? (progbar_count = rs->f_count) : 
								(progbar_count = rs->r_count);
			progbar_quantum = progbar_count/100;
			/* since conversation in one way can start later than in the other one, 
			 * we have to write some silence information for one channel */
			if (rs->f_start_time > rs->r_start_time) {
				f_write_silence = (rs->f_start_time-rs->r_start_time)*8000;
			}
			else if (rs->f_start_time < rs->r_start_time) {
				r_write_silence = (rs->r_start_time-rs->f_start_time)*8000;
			}
			for(;;) {
				if(stop_flag) 
					break;
				if((count > progbar_nextstep) && (count <= progbar_count)) {
					update_progress_dlg(progbar, 
						(gfloat) count/progbar_count, "Saving");
					progbar_nextstep = progbar_nextstep + progbar_quantum;
				}
				count++;
				if(f_write_silence > 0) {
					rread = read(rev_fd, &r_pd, 2);
					f_pd = 0;
					fread = 1;
					f_write_silence--;
				}
				else if(r_write_silence > 0) {
					fread = read(forw_fd, &f_pd, 2);
					r_pd = 0;
					rread = 1;
					r_write_silence--;
				}
				else {
					fread = read(forw_fd, &f_pd, 2); 
					rread = read(rev_fd, &r_pd, 2);
				}
				if ((rread == 0) && (fread == 0)) 
					break;
				*pd = (unsigned char)linear2ulaw( (f_pd + r_pd)/2 );
				rwritten = write(to_fd, pd, 1);
				if ((rwritten < 0) || (rread < 0) || (fread < 0)) {
					close(forw_fd);
					close(rev_fd);
					close(to_fd);
					destroy_progress_dlg(progbar);
					return FALSE;
				}
			}
		}
	}
	destroy_progress_dlg(progbar);
	close(forw_fd);
	close(rev_fd);
	close(to_fd);
	return TRUE;

}

/* menu.c
 * Menu routines
 *
 * $Id: menu.c,v 1.79 2002/11/17 11:43:40 sahlberg Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxxxx>
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <gtk/gtk.h>

#include <string.h>

#include "../menu.h"

#include "main.h"
#include "menu.h"
#include <epan/packet.h>
#include <epan/resolv.h>
#include "prefs.h"
#include "capture_dlg.h"
#include "color_dlg.h"
#include "file_dlg.h"
#include "filter_prefs.h"
#include "find_dlg.h"
#include "goto_dlg.h"
#include "summary_dlg.h"
#include "display_opts.h"
#include "prefs_dlg.h"
#include "packet_win.h"
#include "print.h"
#include "follow_dlg.h"
#include "decode_as_dlg.h"
#include "help_dlg.h"
#include "proto_dlg.h"
#include "proto_hier_stats_dlg.h"
#include "keys.h"
#include <epan/plugins.h>
#include "tcp_graph.h"
#include <epan/epan_dissect.h>
#include "io_stat.h"
#include "rpc_stat.h"
#include "rpc_progs.h"
#include "dcerpc_stat.h"
#include "compat_macros.h"
#include "gtkglobals.h"
#include "tap_rtp.h"

GtkWidget *popup_menu_object;

#define GTK_MENU_FUNC(a) ((GtkItemFactoryCallback)(a))

static void menus_init(void);
static void set_menu_sensitivity (gchar *, gint);

/* This is the GtkItemFactoryEntry structure used to generate new menus.
       Item 1: The menu path. The letter after the underscore indicates an
               accelerator key once the menu is open.
       Item 2: The accelerator key for the entry
       Item 3: The callback function.
       Item 4: The callback action.  This changes the parameters with
               which the function is called.  The default is 0.
       Item 5: The item type, used to define what kind of an item it is.
               Here are the possible values:

               NULL               -> "<Item>"
               ""                 -> "<Item>"
               "<Title>"          -> create a title item
               "<Item>"           -> create a simple item
               "<ImageItem>"      -> create an item holding an image (gtk2)
               "<StockItem>"	  -> create an item holding a stock image (gtk2)
               "<CheckItem>"      -> create a check item
               "<ToggleItem>"     -> create a toggle item
               "<RadioItem>"      -> create a radio item
               <path>             -> path of a radio item to link against
               "<Separator>"      -> create a separator
               "<Tearoff>"        -> create a tearoff separator (gtk2)
               "<Branch>"         -> create an item to hold sub items (optional)
               "<LastBranch>"     -> create a right justified branch
       Item 6: extra data needed for ImageItem and StockItem (gtk2)
    */

/* main menu */
static GtkItemFactoryEntry menu_items[] =
{
    ITEM_FACTORY_ENTRY("/_File", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_STOCK_ENTRY("/File/_Open...", "<control>O", file_open_cmd_cb,
                             0, GTK_STOCK_OPEN),
    ITEM_FACTORY_STOCK_ENTRY("/File/_Close", "<control>W", file_close_cmd_cb,
                             0, GTK_STOCK_CLOSE),
    ITEM_FACTORY_STOCK_ENTRY("/File/_Save", "<control>S", file_save_cmd_cb,
                             0, GTK_STOCK_SAVE),
    ITEM_FACTORY_STOCK_ENTRY("/File/Save _As...", NULL, file_save_as_cmd_cb,
                             0, GTK_STOCK_SAVE_AS),
    ITEM_FACTORY_STOCK_ENTRY("/File/_Reload", "<control>R", file_reload_cmd_cb,
                             0, GTK_STOCK_REFRESH),
    ITEM_FACTORY_ENTRY("/File/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_STOCK_ENTRY("/File/_Print...", NULL, file_print_cmd_cb,
                             0, GTK_STOCK_PRINT),
    ITEM_FACTORY_ENTRY("/File/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/File/Print Pac_ket", "<control>P",
                       file_print_packet_cmd_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/File/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_STOCK_ENTRY("/File/_Quit", "<control>Q", file_quit_cmd_cb,
                             0, GTK_STOCK_QUIT),
    ITEM_FACTORY_ENTRY("/_Edit", NULL, NULL, 0, "<Branch>", NULL),
#if 0
    /* Un-#if this when we actually implement Cut/Copy/Paste. */
    ITEM_FACTORY_STOCK_ENTRY("/Edit/Cut", "<control>X", NULL,
                             0, GTK_STOCK_CUT),
    ITEM_FACTORY_STOCK_ENTRY("/Edit/Copy", "<control>C", NULL,
                             0, GTK_STOCK_COPY),
    ITEM_FACTORY_STOCK_ENTRY("/Edit/Paste", "<control>V", NULL,
                             0, GTK_STOCK_PASTE),
    ITEM_FACTORY_ENTRY("/Edit/<separator>", NULL, NULL, 0, "<Separator>"),
#endif
    ITEM_FACTORY_STOCK_ENTRY("/Edit/_Find Frame...", "<control>F",
                             find_frame_cb, 0, GTK_STOCK_FIND),
    ITEM_FACTORY_STOCK_ENTRY("/Edit/Find _Next", "<control>N", find_next_cb,
                             0, GTK_STOCK_GO_FORWARD),
    ITEM_FACTORY_STOCK_ENTRY("/Edit/Find _Previous", "<control>B",
                             find_previous_cb, 0, GTK_STOCK_GO_BACK),
    ITEM_FACTORY_STOCK_ENTRY("/Edit/_Go To Frame...", "<control>G",
                             goto_frame_cb, 0, GTK_STOCK_JUMP_TO),
    ITEM_FACTORY_ENTRY("/Edit/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/Edit/_Mark Frame", "<control>M", mark_frame_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Edit/Mark _All Frames", NULL, mark_all_frames_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Edit/_Unmark All Frames", NULL, unmark_all_frames_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Edit/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_STOCK_ENTRY("/Edit/_Preferences...", NULL, prefs_cb,
                             0, GTK_STOCK_PREFERENCES),
#ifdef HAVE_LIBPCAP
    ITEM_FACTORY_ENTRY("/Edit/_Capture Filters...", NULL, cfilter_dialog_cb,
                       0, NULL, NULL),
#endif /* HAVE_LIBPCAP */
    ITEM_FACTORY_ENTRY("/Edit/_Display Filters...", NULL, dfilter_dialog_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Edit/P_rotocols...", NULL, proto_cb, 0, NULL, NULL),
#ifdef HAVE_LIBPCAP
    ITEM_FACTORY_ENTRY("/_Capture", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_STOCK_ENTRY("/Capture/_Start...", "<control>K",
                             capture_prep_cb, 0, GTK_STOCK_EXECUTE),
  /*
   * XXX - this doesn't yet work in Win32.
   */
#ifndef _WIN32
    ITEM_FACTORY_STOCK_ENTRY("/Capture/S_top", "<control>E", capture_stop_cb,
                             0, GTK_STOCK_STOP),
#endif /* _WIN32 */
#endif /* HAVE_LIBPCAP */
    ITEM_FACTORY_ENTRY("/_Display", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Display/_Options...", NULL, display_opt_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/_Match", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Display/Match/_Selected", NULL,
                       match_selected_cb_replace_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Match/_Not Selected", NULL,
                       match_selected_cb_not_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Match/_And Selected", NULL,
                       match_selected_cb_and_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Match/_Or Selected", NULL,
                       match_selected_cb_or_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Match/A_nd Not Selected", NULL,
                       match_selected_cb_and_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Match/O_r Not Selected", NULL,
                       match_selected_cb_or_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/_Prepare", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Display/Prepare/_Selected", NULL,
                       prepare_selected_cb_replace_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Prepare/_Not Selected", NULL,
                       prepare_selected_cb_not_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Prepare/_And Selected", NULL,
                       prepare_selected_cb_and_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Prepare/_Or Selected", NULL,
                       prepare_selected_cb_or_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Prepare/A_nd Not Selected", NULL,
                       prepare_selected_cb_and_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Prepare/O_r Not Selected", NULL,
                       prepare_selected_cb_or_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/_Colorize Display...", NULL, color_display_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/Collapse _All", NULL, collapse_all_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/_Expand All", NULL, expand_all_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/_Show Packet In New Window", NULL,
                       new_window_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display/User Specified Decodes...", NULL,
                       decode_show_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/_Tools", NULL, NULL, 0, "<Branch>", NULL),
#ifdef HAVE_PLUGINS
    ITEM_FACTORY_ENTRY("/Tools/_Plugins...", NULL, tools_plugins_cmd_cb,
                       0, NULL, NULL),
#endif /* HAVE_PLUGINS */
    ITEM_FACTORY_ENTRY("/Tools/_Follow TCP Stream", NULL, follow_stream_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/_Decode As...", NULL, decode_as_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/_RTP analysis...", NULL, rtp_analyse_cb,
                       0, NULL, NULL),
/*  {"/Tools/Graph", NULL, NULL, 0, NULL}, future use */
    ITEM_FACTORY_ENTRY("/_Tools/TCP Stream Analysis", NULL, NULL,
                       0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/_Tools/TCP Stream Analysis/Time-Sequence Graph (Stevens)",
                       NULL, tcp_graph_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/_Tools/TCP Stream Analysis/Time-Sequence Graph (tcptrace)",
                       NULL, tcp_graph_cb, 1, NULL, NULL),
    ITEM_FACTORY_ENTRY("/_Tools/TCP Stream Analysis/Throughput Graph", NULL,
                       tcp_graph_cb, 2, NULL, NULL),
    ITEM_FACTORY_ENTRY("/_Tools/TCP Stream Analysis/RTT Graph", NULL,
                       tcp_graph_cb, 3, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/_Summary", NULL, summary_open_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/Protocol Hierarchy Statistics", NULL,
                       proto_hier_stats_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/ONC-RPC", NULL, NULL, 0, "<Branch>",
                       NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/ONC-RPC/RTT", NULL, gtk_rpcstat_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/ONC-RPC/Programs", NULL,
                       gtk_rpcprogs_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/DCE-RPC", NULL, NULL, 0, "<Branch>",
                       NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/DCE-RPC/RTT", NULL, gtk_dcerpcstat_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/IO", NULL, NULL, 0, "<Branch>",
                       NULL),
    ITEM_FACTORY_ENTRY("/Tools/Statistics/IO/IO-Stat", NULL, gtk_iostat_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/_Help", NULL, NULL, 0, "<LastBranch>", NULL),
    ITEM_FACTORY_STOCK_ENTRY("/Help/_Help", NULL, help_cb, 0, GTK_STOCK_HELP),
    ITEM_FACTORY_ENTRY("/Help/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/Help/_About Ethereal...", NULL, about_ethereal,
                       0, NULL, NULL)
};


/* calculate the number of menu_items */
static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);

/* packet list popup */
static GtkItemFactoryEntry packet_list_menu_items[] =
{
    ITEM_FACTORY_ENTRY("/Follow TCP Stream", NULL, follow_stream_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Decode As...", NULL, decode_as_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display Filters...", NULL, dfilter_dialog_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/Mark Frame", NULL, mark_frame_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Match/_Selected", NULL,
                       match_selected_cb_replace_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/_Not Selected", NULL,
                       match_selected_cb_not_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/_And Selected", NULL,
                       match_selected_cb_and_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/_Or Selected", NULL, match_selected_cb_or_plist,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/A_nd Not Selected", NULL,
                       match_selected_cb_and_plist_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/O_r Not Selected", NULL,
                       match_selected_cb_or_plist_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_Selected", NULL,
                       prepare_selected_cb_replace_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_Not Selected", NULL,
                       prepare_selected_cb_not_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_And Selected", NULL,
                       prepare_selected_cb_and_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_Or Selected", NULL,
                       prepare_selected_cb_or_plist, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/A_nd Not Selected", NULL,
                       prepare_selected_cb_and_plist_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/O_r Not Selected", NULL,
                       prepare_selected_cb_or_plist_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/Colorize Display...", NULL, color_display_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Print...", NULL, file_print_cmd_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Print Packet", NULL, file_print_packet_cmd_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Show Packet In New Window", NULL, new_window_cb,
                       0, NULL, NULL),
};

static GtkItemFactoryEntry tree_view_menu_items[] =
{
    ITEM_FACTORY_ENTRY("/Follow TCP Stream", NULL, follow_stream_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Decode As...", NULL, decode_as_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display Filters...", NULL, dfilter_dialog_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/Resolve Name", NULL, resolve_name_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Protocol Properties...", NULL, properties_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Match/_Selected", NULL,
                       match_selected_cb_replace_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/_Not Selected", NULL,
                       match_selected_cb_not_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/_And Selected", NULL,
                       match_selected_cb_and_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/_Or Selected", NULL, match_selected_cb_or_ptree,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/A_nd Not Selected", NULL,
                       match_selected_cb_and_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Match/O_r Not Selected", NULL,
                       match_selected_cb_or_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare", NULL, NULL, 0, "<Branch>", NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_Selected", NULL,
                       prepare_selected_cb_replace_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_Not Selected", NULL,
                       prepare_selected_cb_not_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_And Selected", NULL,
                       prepare_selected_cb_and_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/_Or Selected", NULL,
                       prepare_selected_cb_or_ptree, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/A_nd Not Selected", NULL,
                       prepare_selected_cb_and_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Prepare/O_r Not Selected", NULL,
                       prepare_selected_cb_or_ptree_not, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/<separator>", NULL, NULL, 0, "<Separator>", NULL),
    ITEM_FACTORY_ENTRY("/Collapse All", NULL, collapse_all_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Expand All", NULL, expand_all_cb, 0, NULL, NULL)
};

static GtkItemFactoryEntry hexdump_menu_items[] =
{
    ITEM_FACTORY_ENTRY("/Follow TCP Stream", NULL, follow_stream_cb,
                       0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Decode As...", NULL, decode_as_cb, 0, NULL, NULL),
    ITEM_FACTORY_ENTRY("/Display Filters...", NULL, dfilter_dialog_cb,
                       0, NULL, NULL)
};

static int initialize = TRUE;
static GtkItemFactory *factory = NULL;
static GtkItemFactory *packet_list_menu_factory = NULL;
static GtkItemFactory *tree_view_menu_factory = NULL;
static GtkItemFactory *hexdump_menu_factory = NULL;

static GSList *popup_menu_list = NULL;

static GtkAccelGroup *grp;

void
get_main_menu(GtkWidget ** menubar, GtkAccelGroup ** table) {

  grp = gtk_accel_group_new();

  if (initialize) {
    popup_menu_object = gtk_widget_new(GTK_TYPE_WIDGET, NULL);
    menus_init();
  }

  if (menubar)
    *menubar = factory->widget;

  if (table)
    *table = grp;
}

static void
menus_init(void) {

  if (initialize) {
    initialize = FALSE;

    /* popup */

    packet_list_menu_factory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL);
    gtk_item_factory_create_items_ac(packet_list_menu_factory, sizeof(packet_list_menu_items)/sizeof(packet_list_menu_items[0]), packet_list_menu_items, popup_menu_object, 2);
    OBJECT_SET_DATA(popup_menu_object, PM_PACKET_LIST_KEY,
                    packet_list_menu_factory->widget);
    popup_menu_list = g_slist_append((GSList *)popup_menu_list, packet_list_menu_factory);

    tree_view_menu_factory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL);
    gtk_item_factory_create_items_ac(tree_view_menu_factory, sizeof(tree_view_menu_items)/sizeof(tree_view_menu_items[0]), tree_view_menu_items, popup_menu_object, 2);
    OBJECT_SET_DATA(popup_menu_object, PM_TREE_VIEW_KEY,
                    tree_view_menu_factory->widget);
    popup_menu_list = g_slist_append((GSList *)popup_menu_list, tree_view_menu_factory);

    hexdump_menu_factory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL);
    gtk_item_factory_create_items_ac(hexdump_menu_factory, sizeof(hexdump_menu_items)/sizeof(hexdump_menu_items[0]), hexdump_menu_items, popup_menu_object, 2);
    OBJECT_SET_DATA(popup_menu_object, PM_HEXDUMP_KEY,
                    hexdump_menu_factory->widget);
    popup_menu_list = g_slist_append((GSList *)popup_menu_list, hexdump_menu_factory);

    factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", grp);
    gtk_item_factory_create_items_ac(factory, nmenu_items, menu_items, NULL,2);
    set_menus_for_unsaved_capture_file(FALSE);
    set_menus_for_capture_file(FALSE);
#if 0
    /* Un-#if this when we actually implement Cut/Copy/Paste.
       Then make sure you enable them when they can be done. */
    set_menu_sensitivity("/Edit/Cut", FALSE);
    set_menu_sensitivity("/Edit/Copy", FALSE);
    set_menu_sensitivity("/Edit/Paste", FALSE);
#endif
    set_menus_for_captured_packets(FALSE);
    set_menus_for_selected_packet(FALSE);
    set_menus_for_selected_tree_row(FALSE);
  }
}

void
set_menu_sensitivity_meat(GtkItemFactory *ifactory, gchar *path, gint val) {
	GtkWidget *menu = NULL;

	if((menu = gtk_item_factory_get_widget(ifactory, path)) != NULL) {
		gtk_widget_set_sensitive(menu,val);
	}
}

/* Enable/disable menu sensitivity                       */
/* /menu/path            - old functionality             */
/* <MenuName>/menu/path  - new functionality             */
/* MenuName: <Main>, <PacketList>, <TreeView>, <HexDump> */
static void
set_menu_sensitivity (gchar *path, gint val) {
  GSList *menu_list = popup_menu_list;
  gchar *prefix;
  gchar *shortpath;

  if ('<' == *path) {
    /* New functionality => selective enable/disable per menu */
    prefix=strchr(path, '/');
    shortpath=strrchr(prefix, '/');

    if (0 == strncmp(path, "<Main>", 6))
      set_menu_sensitivity_meat(factory, prefix, val);
    else if (0 == strncmp(path, "<PacketList>", 12))
      set_menu_sensitivity_meat(packet_list_menu_factory, shortpath, val);
    else if (0 == strncmp(path, "<TreeView>", 10))
      set_menu_sensitivity_meat(tree_view_menu_factory, shortpath, val);
    else if (0 == strncmp(path, "<HexDump>", 9))
      set_menu_sensitivity_meat(hexdump_menu_factory, shortpath, val);
  } else {
    /* Old functionality => enable/disable all menus with same shortpath */
    shortpath = strrchr(path, '/');

    set_menu_sensitivity_meat(factory, path, val);

    while (menu_list != NULL) {
  	  set_menu_sensitivity_meat(menu_list->data, shortpath, val);
	  menu_list = g_slist_next(menu_list);
    }
  }
}

void
set_menu_object_data_meat(GtkItemFactory *ifactory, gchar *path, gchar *key, gpointer data)
{
	GtkWidget *menu = NULL;

	if ((menu = gtk_item_factory_get_widget(ifactory, path)) != NULL)
		OBJECT_SET_DATA(menu, key, data);
}

void
set_menu_object_data (gchar *path, gchar *key, gpointer data) {
  GSList *menu_list = popup_menu_list;
  gchar *shortpath = strrchr(path, '/');

  set_menu_object_data_meat(factory, path, key, data);
  while (menu_list != NULL) {
  	set_menu_object_data_meat(menu_list->data, shortpath, key, data);
	menu_list = g_slist_next(menu_list);
  }
}

gint
popup_menu_handler(GtkWidget *widget, GdkEvent *event, gpointer data)
{
    GtkWidget *menu = NULL;
    GdkEventButton *event_button = NULL;
    GtkCList *packet_list = NULL;
    gint row, column;

    if(widget == NULL || event == NULL || data == NULL) {
        return FALSE;
    }

    /*
     * If we ever want to make the menu differ based on what row
     * and/or column we're above, we'd use "gtk_clist_get_selection_info()"
     * to find the row and column number for the coordinates; a CTree is,
     * I guess, like a CList with one column(?) and the expander widget
     * as a pixmap.
     */
    /* Check if we are on packet_list object */
    if (widget == OBJECT_GET_DATA(popup_menu_object, E_MPACKET_LIST_KEY)) {
        packet_list=GTK_CLIST(widget);
        if (gtk_clist_get_selection_info(GTK_CLIST(packet_list),
                                         ((GdkEventButton *)event)->x,
                                         ((GdkEventButton *)event)->y,&row,&column)) {
            OBJECT_SET_DATA(popup_menu_object, E_MPACKET_LIST_ROW_KEY,
                            GINT_TO_POINTER(row));
            OBJECT_SET_DATA(popup_menu_object, E_MPACKET_LIST_COL_KEY,
                            GINT_TO_POINTER(column));
        }
    }
    menu = (GtkWidget *)data;
    if(event->type == GDK_BUTTON_PRESS) {
        event_button = (GdkEventButton *) event;

        if(event_button->button == 3) {
            gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
                           event_button->button,
                           event_button->time);
            SIGNAL_EMIT_STOP_BY_NAME(widget, "button_press_event");
            return TRUE;
        }
    }
#if GTK_MAJOR_VERSION >= 2
    if (widget == tree_view && event->type == GDK_2BUTTON_PRESS) {
        GtkTreePath      *path;

        if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
                                          ((GdkEventButton *)event)->x,
                                          ((GdkEventButton *)event)->y,
                                          &path, NULL, NULL, NULL))
        {
            if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
                gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
            else
                gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path,
                                         FALSE);
            gtk_tree_path_free(path);
        }
    }
#endif
    return FALSE;
}

/* Enable or disable menu items based on whether you have a capture file
   you've finished reading. */
void
set_menus_for_capture_file(gboolean have_capture_file)
{
  set_menu_sensitivity("/File/Open...", have_capture_file);
  set_menu_sensitivity("/File/Save As...", have_capture_file);
  set_menu_sensitivity("/File/Close", have_capture_file);
  set_menu_sensitivity("/File/Reload", have_capture_file);
}

/* Enable or disable menu items based on whether you have an unsaved
   capture file you've finished reading. */
void
set_menus_for_unsaved_capture_file(gboolean have_unsaved_capture_file)
{
  set_menu_sensitivity("/File/Save", have_unsaved_capture_file);
}

/* Enable or disable menu items based on whether there's a capture in
   progress. */
void
set_menus_for_capture_in_progress(gboolean capture_in_progress)
{
  set_menu_sensitivity("/File/Open...", !capture_in_progress);
  set_menu_sensitivity("/Capture/Start...", !capture_in_progress);
  /*
   * XXX - this doesn't yet work in Win32.
   */
#ifndef _WIN32
  set_menu_sensitivity("/Capture/Stop", capture_in_progress);
#endif
}

/* Enable or disable menu items based on whether you have some captured
   packets. */
void
set_menus_for_captured_packets(gboolean have_captured_packets)
{
  set_menu_sensitivity("/File/Print...", have_captured_packets);
  set_menu_sensitivity("/Edit/Find Frame...", have_captured_packets);
  set_menu_sensitivity("/Edit/Find Next", have_captured_packets);
  set_menu_sensitivity("/Edit/Find Previous", have_captured_packets);
  set_menu_sensitivity("/Edit/Go To Frame...", have_captured_packets);
  set_menu_sensitivity("/Display/Colorize Display...", have_captured_packets);
  set_menu_sensitivity("/Tools/Summary", have_captured_packets);
  set_menu_sensitivity("/Tools/Protocol Hierarchy Statistics", have_captured_packets);
  set_menu_sensitivity("<PacketList>/Display/Match", have_captured_packets);
  set_menu_sensitivity("<PacketList>/Display/Prepare", have_captured_packets);
}

/* Enable or disable menu items based on whether a packet is selected. */
void
set_menus_for_selected_packet(gboolean have_selected_packet)
{
  set_menu_sensitivity("/File/Print Packet", have_selected_packet);
  set_menu_sensitivity("/Edit/Mark Frame", have_selected_packet);
  set_menu_sensitivity("/Edit/Mark All Frames", have_selected_packet);
  set_menu_sensitivity("/Edit/Unmark All Frames", have_selected_packet);
  set_menu_sensitivity("/Display/Collapse All", have_selected_packet);
  set_menu_sensitivity("/Display/Expand All", have_selected_packet);
  set_menu_sensitivity("/Display/Show Packet In New Window", have_selected_packet);
  set_menu_sensitivity("/Tools/Follow TCP Stream",
      have_selected_packet ? (cfile.edt->pi.ipproto == 6) : FALSE);
  set_menu_sensitivity("/Tools/Decode As...",
      have_selected_packet && decode_as_ok());
  set_menu_sensitivity("/Resolve Name",
      have_selected_packet && g_resolv_flags == 0);
  set_menu_sensitivity("/Tools/TCP Stream Analysis",
            have_selected_packet ? (cfile.edt->pi.ipproto == 6) : FALSE);
}

/* Enable or disable menu items based on whether a tree row is selected
   and and on whether a "Match" can be done. */
void
set_menus_for_selected_tree_row(gboolean have_selected_tree)
{
  gboolean properties = FALSE;

  if (finfo_selected) {
	header_field_info *hfinfo = finfo_selected->hfinfo;
	if (hfinfo->parent == -1) {
	  properties = prefs_is_registered_protocol(hfinfo->abbrev);
	} else {
	  properties = prefs_is_registered_protocol(proto_registrar_get_abbrev(hfinfo->parent));
	}
	set_menu_sensitivity("<Main>/Display/Match",
	  proto_can_match_selected(finfo_selected));
	set_menu_sensitivity("<TreeView>/Display/Match",
	  proto_can_match_selected(finfo_selected));
	set_menu_sensitivity("<Main>/Display/Prepare",
	  proto_can_match_selected(finfo_selected));
	set_menu_sensitivity("<TreeView>/Display/Prepare",
	  proto_can_match_selected(finfo_selected));
  } else {
	set_menu_sensitivity("<Main>/Display/Match", FALSE);
	set_menu_sensitivity("<TreeView>/Display/Match", FALSE);
	set_menu_sensitivity("<Main>/Display/Prepare", FALSE);
	set_menu_sensitivity("<TreeView>/Display/Prepare", FALSE);
  }

  set_menu_sensitivity("/Protocol Properties...", have_selected_tree && properties);
}