Damn the torpedos[1], commit it anyway.
[obnox/wireshark/wip.git] / gtk / follow_dlg.c
1 /* follow_dlg.c
2  *
3  * $Id: follow_dlg.c,v 1.13 2001/04/10 12:07:39 gram Exp $
4  *
5  * Ethereal - Network traffic analyzer
6  * By Gerald Combs <gerald@zing.org>
7  * Copyright 2000 Gerald Combs
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  * 
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <gtk/gtk.h>
30
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34
35 #include <errno.h>
36
37 #ifdef HAVE_SYS_TYPES_H
38 #include <sys/types.h>
39 #endif
40
41 #ifdef HAVE_SYS_STAT_H
42 #include <sys/stat.h>
43 #endif
44
45 #ifdef HAVE_IO_H
46 #include <io.h>                 /* open/close on win32 */
47 #endif
48
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52
53 #ifdef NEED_SNPRINTF_H
54 # include "snprintf.h"
55 #endif
56
57 #include "color.h"
58 #include "color_utils.h"
59 #include "file.h"
60 #include "follow_dlg.h"
61 #include "follow.h"
62 #include "dlg_utils.h"
63 #include "keys.h"
64 #include "globals.h"
65 #include "gtkglobals.h"
66 #include "main.h"
67 #include "simple_dialog.h"
68 #include "packet-ipv6.h"
69 #include "prefs.h"
70 #include "resolv.h"
71 #include "util.h"
72 #include "ui_util.h"
73
74 /* Show Stream */
75 typedef enum {
76         FROM_CLIENT,
77         FROM_SERVER,
78         BOTH_HOSTS
79 } show_stream_t;
80
81 /* Show Type */
82 typedef enum {
83         SHOW_ASCII,
84         SHOW_EBCDIC,
85         SHOW_HEXDUMP
86 } show_type_t;
87
88 typedef struct {
89         show_stream_t   show_stream;
90         show_type_t     show_type;
91         char            data_out_filename[128 + 1];
92         GtkWidget       *text;
93         GtkWidget       *ascii_bt;
94         GtkWidget       *ebcdic_bt;
95         GtkWidget       *hexdump_bt;
96         GtkWidget       *follow_save_as_w;
97         gboolean        is_ipv6;
98 } follow_info_t;
99
100 static void follow_destroy_cb(GtkWidget * win, gpointer data);
101 static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
102 static void follow_load_text(follow_info_t *follow_info);
103 static void follow_print_stream(GtkWidget * w, gpointer parent_w);
104 static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
105 static void follow_save_as_ok_cb(GtkWidget * w, GtkFileSelection * fs);
106 static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
107 static void follow_stream_om_both(GtkWidget * w, gpointer data);
108 static void follow_stream_om_client(GtkWidget * w, gpointer data);
109 static void follow_stream_om_server(GtkWidget * w, gpointer data);
110
111
112 FILE *data_out_file = NULL;
113
114
115 #define E_FOLLOW_INFO_KEY "follow_info_key"
116
117 /* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
118    so we can redraw them all if the colors or font changes. */
119 static GList *follow_infos;
120
121 /* Add a "follow_info_t" structure to the list. */
122 static void
123 remember_follow_info(follow_info_t *follow_info)
124 {
125   follow_infos = g_list_append(follow_infos, follow_info);
126 }
127
128 /* Remove a "follow_info_t" structure from the list. */
129 static void
130 forget_follow_info(follow_info_t *follow_info)
131 {
132   follow_infos = g_list_remove(follow_infos, follow_info);
133 }
134
135 static void
136 follow_redraw(gpointer data, gpointer user_data)
137 {
138         follow_load_text((follow_info_t *)data);
139 }
140
141 /* Redraw the text in all "Follow TCP Stream" windows. */
142 void
143 follow_redraw_all(void)
144 {
145         g_list_foreach(follow_infos, follow_redraw, NULL);
146 }
147
148 /* Follow the TCP stream, if any, to which the last packet that we called
149    a dissection routine on belongs (this might be the most recently
150    selected packet, or it might be the last packet in the file). */
151 void
152 follow_stream_cb(GtkWidget * w, gpointer data)
153 {
154         GtkWidget       *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
155         GtkWidget       *hbox, *button, *radio_bt;
156         GtkWidget       *stream_om, *stream_menu, *stream_mi;
157         int             tmp_fd;
158         gchar           *follow_filter;
159         const char      *hostname0, *hostname1;
160         char            *port0, *port1;
161         char            string[128];
162         follow_tcp_stats_t stats;
163         follow_info_t   *follow_info;
164
165         /* we got tcp so we can follow */
166         if (pi.ipproto != 6) {
167                 simple_dialog(ESD_TYPE_CRIT, NULL,
168                               "Error following stream.  Please make\n"
169                               "sure you have a TCP packet selected.");
170                 return;
171         }
172
173         follow_info = g_new0(follow_info_t, 1);
174
175         /* Create a temporary file into which to dump the reassembled data
176            from the TCP stream, and set "data_out_file" to refer to it, so
177            that the TCP code will write to it.
178
179            XXX - it might be nicer to just have the TCP code directly
180            append stuff to the text widget for the TCP stream window,
181            if we can arrange that said window not pop up until we're
182            done. */
183         tmp_fd = create_tempfile(follow_info->data_out_filename,
184                         sizeof follow_info->data_out_filename, "follow");
185
186         if (tmp_fd == -1) {
187             simple_dialog(ESD_TYPE_WARN, NULL,
188                           "Could not create temporary file %s: %s",
189                           follow_info->data_out_filename, strerror(errno));
190             g_free(follow_info);
191             return;
192         }
193
194         data_out_file = fdopen(tmp_fd, "wb");
195         if (data_out_file == NULL) {
196             simple_dialog(ESD_TYPE_WARN, NULL,
197                           "Could not create temporary file %s: %s",
198                           follow_info->data_out_filename, strerror(errno));
199             close(tmp_fd);
200             unlink(follow_info->data_out_filename);
201             g_free(follow_info);
202             return;
203         }
204
205         /* Create a new filter that matches all packets in the TCP stream,
206            and set the display filter entry accordingly */
207         reset_tcp_reassembly();
208         follow_filter = build_follow_filter(&pi);
209
210         /* Set the display filter entry accordingly */
211         filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);
212         gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
213
214         /* Run the display filter so it goes in effect. */
215         filter_packets(&cfile, follow_filter);
216
217         /* The data_out_file should now be full of the streams information */
218         fclose(data_out_file);
219
220         /* The data_out_filename file now has all the text that was in the session */
221         streamwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
222         gtk_widget_set_name(streamwindow, "TCP stream window");
223
224         gtk_signal_connect(GTK_OBJECT(streamwindow), "destroy",
225                            GTK_SIGNAL_FUNC(follow_destroy_cb), NULL);
226
227         if (incomplete_tcp_stream) {
228             gtk_window_set_title(GTK_WINDOW(streamwindow),
229                                  "Contents of TCP stream (incomplete)");
230         } else {
231             gtk_window_set_title(GTK_WINDOW(streamwindow),
232                                  "Contents of TCP stream");
233         }
234         gtk_widget_set_usize(GTK_WIDGET(streamwindow), DEF_WIDTH,
235                              DEF_HEIGHT);
236         gtk_container_border_width(GTK_CONTAINER(streamwindow), 2);
237
238         /* setup the container */
239         vbox = gtk_vbox_new(FALSE, 0);
240         gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
241
242         /* create a scrolled window for the text */
243         txt_scrollw = gtk_scrolled_window_new(NULL, NULL);
244         gtk_box_pack_start(GTK_BOX(vbox), txt_scrollw, TRUE, TRUE, 0);
245         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(txt_scrollw),
246                                        GTK_POLICY_NEVER,
247                                        GTK_POLICY_ALWAYS);
248         set_scrollbar_placement_scrollw(txt_scrollw,
249                                         prefs.gui_scrollbar_on_right);
250         remember_scrolled_window(txt_scrollw);
251
252         /* create a text box */
253         text = gtk_text_new(NULL, NULL);
254         gtk_text_set_editable(GTK_TEXT(text), FALSE);
255         gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
256         follow_info->text = text;
257
258         /* Create hbox */
259         hbox = gtk_hbox_new(FALSE, 1);
260         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
261
262
263         /* Stream to show */
264         follow_tcp_stats(&stats);
265
266         if (stats.is_ipv6) {
267           struct e_in6_addr ipaddr;
268           memcpy(&ipaddr, stats.ip_address[0], 16);
269           hostname0 = get_hostname6(&ipaddr);
270           memcpy(&ipaddr, stats.ip_address[0], 16);
271           hostname1 = get_hostname6(&ipaddr);
272         } else {
273           guint32 ipaddr;
274           memcpy(&ipaddr, stats.ip_address[0], 4);
275           hostname0 = get_hostname(ipaddr);
276           memcpy(&ipaddr, stats.ip_address[1], 4);
277           hostname1 = get_hostname(ipaddr);
278         }
279
280         port0 = get_tcp_port(stats.tcp_port[0]);
281         port1 = get_tcp_port(stats.tcp_port[1]);
282
283         follow_info->is_ipv6 = stats.is_ipv6;
284
285         stream_om = gtk_option_menu_new();
286         stream_menu = gtk_menu_new();
287
288         /* Both Hosts */
289         snprintf(string, sizeof(string),
290                  "Entire conversation (%u bytes)",
291                  stats.bytes_written[0] + stats.bytes_written[1]);
292         stream_mi = gtk_menu_item_new_with_label(string);
293         gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
294                            GTK_SIGNAL_FUNC(follow_stream_om_both), follow_info);
295         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
296         gtk_widget_show(stream_mi);
297         follow_info->show_stream = BOTH_HOSTS;
298
299         /* Host 0 --> Host 1 */
300         snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
301                  hostname0, port0, hostname1, port1,
302                  stats.bytes_written[0]);
303         stream_mi = gtk_menu_item_new_with_label(string);
304         gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
305                            GTK_SIGNAL_FUNC(follow_stream_om_client), follow_info);
306         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
307         gtk_widget_show(stream_mi);
308
309         /* Host 1 --> Host 0 */
310         snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
311                  hostname1, port1, hostname0, port0,
312                  stats.bytes_written[1]);
313         stream_mi = gtk_menu_item_new_with_label(string);
314         gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
315                            GTK_SIGNAL_FUNC(follow_stream_om_server), follow_info);
316         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
317         gtk_widget_show(stream_mi);
318
319         gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
320         /* Set history to 0th item, i.e., the first item. */
321         gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
322         gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
323
324         /* ASCII radio button */
325         radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
326         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
327         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
328         gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
329                            GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
330                            follow_info);
331         follow_info->ascii_bt = radio_bt;
332         follow_info->show_type = SHOW_ASCII;
333
334         /* EBCDIC radio button */
335         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
336                                             (GTK_RADIO_BUTTON(radio_bt)),
337                                             "EBCDIC");
338         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
339         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
340         gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
341                            GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
342                            follow_info);
343         follow_info->ebcdic_bt = radio_bt;
344
345         /* HEX DUMP radio button */
346         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
347                                             (GTK_RADIO_BUTTON(radio_bt)),
348                                             "Hex Dump");
349         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
350         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
351         gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
352                            GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
353                            follow_info);
354         follow_info->hexdump_bt = radio_bt;
355
356         /* Create Close Button */
357         button = gtk_button_new_with_label("Close");
358         gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
359                                   GTK_SIGNAL_FUNC(gtk_widget_destroy),
360                                   GTK_OBJECT(streamwindow));
361         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
362
363         /* Catch the "key_press_event" signal in the window, so that we can catch
364         the ESC key being pressed and act as if the "Cancel" button had
365         been selected. */
366         dlg_set_cancel(streamwindow, button);
367
368         /* Create Save As Button */
369         button = gtk_button_new_with_label("Save As");
370         gtk_signal_connect(GTK_OBJECT(button), "clicked",
371                            GTK_SIGNAL_FUNC(follow_save_as_cmd_cb),
372                            follow_info);
373         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
374
375         /* Create Print Button */
376         button = gtk_button_new_with_label("Print");
377         gtk_signal_connect(GTK_OBJECT(button), "clicked",
378                            GTK_SIGNAL_FUNC(follow_print_stream), follow_info);
379         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
380
381
382         /* Tuck away the follow_info object into the window */
383         gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_INFO_KEY,
384                             follow_info);
385
386         follow_load_text(follow_info);
387         remember_follow_info(follow_info);
388
389         data_out_file = NULL;
390
391         /* Make sure this widget gets destroyed if we quit the main loop,
392            so that if we exit, we clean up any temporary files we have
393            for "Follow TCP Stream" windows. */
394         gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
395         gtk_widget_show_all(streamwindow);
396 }
397
398 /* The destroy call back has the responsibility of
399  * unlinking the temporary file */
400 static void
401 follow_destroy_cb(GtkWidget *w, gpointer data)
402 {
403         follow_info_t   *follow_info;
404
405         follow_info = gtk_object_get_data(GTK_OBJECT(w), E_FOLLOW_INFO_KEY);
406         unlink(follow_info->data_out_filename);
407         gtk_widget_destroy(w);
408         forget_follow_info(follow_info);
409         g_free(follow_info);
410 }
411
412 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
413  * 3 different functions here? */
414 static void
415 follow_stream_om_both(GtkWidget *w, gpointer data)
416 {
417         follow_info_t   *follow_info = data;
418         follow_info->show_stream = BOTH_HOSTS;
419         follow_load_text(follow_info);
420 }
421
422 static void
423 follow_stream_om_client(GtkWidget *w, gpointer data)
424 {
425         follow_info_t   *follow_info = data;
426         follow_info->show_stream = FROM_CLIENT;
427         follow_load_text(follow_info);
428 }
429
430 static void
431 follow_stream_om_server(GtkWidget *w, gpointer data)
432 {
433         follow_info_t   *follow_info = data;
434         follow_info->show_stream = FROM_SERVER;
435         follow_load_text(follow_info);
436 }
437
438
439 /* Handles the ASCII/EBCDIC toggling */
440 static void
441 follow_charset_toggle_cb(GtkWidget * w, gpointer data)
442 {
443         follow_info_t   *follow_info = data;
444
445         if (GTK_TOGGLE_BUTTON(follow_info->ebcdic_bt)->active)
446                 follow_info->show_type = SHOW_EBCDIC;
447         else if (GTK_TOGGLE_BUTTON(follow_info->hexdump_bt)->active)
448                 follow_info->show_type = SHOW_HEXDUMP;
449         else if (GTK_TOGGLE_BUTTON(follow_info->ascii_bt)->active)
450                 follow_info->show_type = SHOW_ASCII;
451         else
452                 g_assert_not_reached();
453
454         follow_load_text(follow_info);
455 }
456
457 #define FLT_BUF_SIZE 1024
458 static void
459 follow_read_stream(follow_info_t *follow_info,
460                    void (*print_line) (char *, int, gboolean, void *),
461                    void *arg)
462 {
463     tcp_stream_chunk    sc;
464     int                 bcount, iplen;
465     guint8              client_addr[MAX_IPADDR_LEN];
466     guint16             client_port = 0;
467     gboolean            is_server;
468     guint16             current_pos, global_client_pos = 0, global_server_pos = 0;
469     guint16             *global_pos;
470     gboolean            skip;
471
472     iplen = (follow_info->is_ipv6) ? 16 : 4;
473      
474     data_out_file = fopen(follow_info->data_out_filename, "rb");
475     if (data_out_file) {
476         char buffer[FLT_BUF_SIZE];
477         int nchars;
478         while (fread(&sc, 1, sizeof(sc), data_out_file)) {
479             if (client_port == 0) {
480                 memcpy(client_addr, sc.src_addr, iplen);
481                 client_port = sc.src_port;
482             }
483             skip = FALSE;
484             if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
485                 client_port == sc.src_port) {
486                 is_server = FALSE;
487                 global_pos = &global_client_pos;
488                 if (follow_info->show_stream == FROM_SERVER) {
489                         skip = TRUE;
490                 }
491             }
492             else {
493                 is_server = TRUE;
494                 global_pos = &global_server_pos;
495                 if (follow_info->show_stream == FROM_CLIENT) {
496                         skip = TRUE;
497                 }
498             }
499
500             while (sc.dlen > 0) {
501                 bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
502                 nchars = fread(buffer, 1, bcount, data_out_file);
503                 if (nchars == 0)
504                     break;
505                 sc.dlen -= bcount;
506                 if (!skip) {
507                     switch (follow_info->show_type) {
508                         case SHOW_EBCDIC:
509                             /* If our native arch is ASCII, call: */
510                             EBCDIC_to_ASCII(buffer, nchars);
511                             (*print_line) (buffer, nchars, is_server, arg);
512                             break;
513                         case SHOW_ASCII:
514                             /* If our native arch is EBCDIC, call:
515                              * ASCII_TO_EBCDIC(buffer, nchars);
516                              */
517                             (*print_line) (buffer, nchars, is_server, arg);
518                             break;
519                         case SHOW_HEXDUMP:
520                             current_pos = 0;
521                             while (current_pos < nchars) {
522                                 gchar hexbuf[256];
523                                 gchar hexchars[] = "0123456789abcdef";
524                                 int i, cur;
525                                 /* is_server indentation : put 63 spaces at the begenning
526                                  * of the string */
527                                 sprintf(hexbuf, (is_server && 
528                                         follow_info->show_stream == BOTH_HOSTS) ?
529                                         "                                 "
530                                         "                              %08X  " :
531                                         "%08X  ", *global_pos);
532                                 cur = strlen(hexbuf);
533                                 for (i = 0; i < 16 && current_pos + i < nchars;
534                                      i++) {
535                                     hexbuf[cur++] =
536                                         hexchars[(buffer[current_pos + i] & 0xf0)
537                                                  >> 4];
538                                     hexbuf[cur++] =
539                                         hexchars[buffer[current_pos + i] & 0x0f];
540                                     if (i == 7) {
541                                         hexbuf[cur++] = ' ';
542                                         hexbuf[cur++] = ' ';
543                                     } else if (i != 15)
544                                         hexbuf[cur++] = ' ';
545                                 }
546                                 current_pos += i;
547                                 (*global_pos) += i;
548                                 hexbuf[cur++] = '\n';
549                                 hexbuf[cur] = 0;
550                                 (*print_line) (hexbuf, strlen(hexbuf), is_server, arg);
551                             }
552                             break;
553                     }
554                 }
555             }
556         }
557         if (ferror(data_out_file)) {
558             simple_dialog(ESD_TYPE_WARN, NULL,
559                           "Error reading temporary file %s: %s", follow_info->data_out_filename,
560                           strerror(errno));
561         }
562         fclose(data_out_file);
563         data_out_file = NULL;
564     } else {
565         simple_dialog(ESD_TYPE_WARN, NULL,
566                       "Could not open temporary file %s: %s", follow_info->data_out_filename,
567                       strerror(errno));
568     }
569 }
570
571 /*
572  * XXX - for text printing, we probably want to wrap lines at 80 characters;
573  * for PostScript printing, we probably want to wrap them at the appropriate
574  * width, and perhaps put some kind of dingbat (to use the technical term)
575  * to indicate a wrapped line, along the lines of what's done when displaying
576  * this in a window, as per Warren Young's suggestion.
577  *
578  * For now, we support only text printing.
579  */
580 static void
581 follow_print_text(char *buffer, int nchars, gboolean is_server, void *arg)
582 {
583     FILE *fh = arg;
584
585     fwrite(buffer, nchars, 1, fh);
586 }
587
588 static void
589 follow_print_stream(GtkWidget * w, gpointer data)
590 {
591     FILE                *fh;
592     gboolean            to_file;
593     char                *print_dest;
594     follow_info_t       *follow_info = data;
595
596     switch (prefs.pr_dest) {
597     case PR_DEST_CMD:
598         print_dest = prefs.pr_cmd;
599         to_file = FALSE;
600         break;
601
602     case PR_DEST_FILE:
603         print_dest = prefs.pr_file;
604         to_file = TRUE;
605         break;
606     default:                    /* "Can't happen" */
607         simple_dialog(ESD_TYPE_CRIT, NULL,
608                       "Couldn't figure out where to send the print "
609                       "job. Check your preferences.");
610         return;
611     }
612
613     fh = open_print_dest(to_file, print_dest);
614     if (fh == NULL) {
615         switch (to_file) {
616         case FALSE:
617             simple_dialog(ESD_TYPE_WARN, NULL,
618                           "Couldn't run print command %s.", prefs.pr_cmd);
619             break;
620
621         case TRUE:
622             simple_dialog(ESD_TYPE_WARN, NULL,
623                           file_write_error_message(errno), prefs.pr_file);
624             break;
625         }
626         return;
627     }
628
629     print_preamble(fh, PR_FMT_TEXT);
630     follow_read_stream(follow_info, follow_print_text, fh);
631     print_finale(fh, PR_FMT_TEXT);
632     close_print_dest(to_file, fh);
633 }
634
635 static void
636 follow_add_to_gtk_text(char *buffer, int nchars, gboolean is_server,
637                        void *arg)
638 {
639     GtkWidget *text = arg;
640     GdkColor   fg, bg;
641
642 #ifdef _WIN32
643     /* While our isprint() hack is in place, we
644      * have to use convert some chars to '.' in order
645      * to be able to see the data we *should* see
646      * in the GtkText widget.
647      */
648     int i;
649
650     for (i = 0; i < nchars; i++) {
651             if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
652                     continue;
653             }
654             else if (! isprint(buffer[i])) {
655                     buffer[i] = '.';
656             }
657     }
658 #endif
659
660     if (is_server) {
661         color_t_to_gdkcolor(&fg, &prefs.st_server_fg);
662         color_t_to_gdkcolor(&bg, &prefs.st_server_bg);
663     } else {
664         color_t_to_gdkcolor(&fg, &prefs.st_client_fg);
665         color_t_to_gdkcolor(&bg, &prefs.st_client_bg);
666     }
667     gtk_text_insert(GTK_TEXT(text), m_r_font, &fg, &bg, buffer, nchars);
668 }
669
670 static void
671 follow_load_text(follow_info_t *follow_info)
672 {
673     int bytes_already;
674
675     /* Delete any info already in text box */
676     bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
677     if (bytes_already > 0) {
678         gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
679         gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
680     }
681
682     /* stop the updates while we fill the text box */
683     gtk_text_freeze(GTK_TEXT(follow_info->text));
684     follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
685     gtk_text_thaw(GTK_TEXT(follow_info->text));
686 }
687
688
689 /*
690  * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
691  * any, so that if somebody tries to do "Save"
692  * while there's already a "Save TCP Follow Stream" window up, we just pop
693  * up the existing one, rather than creating a new one.
694  */
695 static void
696 follow_save_as_cmd_cb(GtkWidget *w, gpointer data)
697 {
698     GtkWidget           *ok_bt, *new_win;
699     follow_info_t       *follow_info = data;
700
701     if (follow_info->follow_save_as_w != NULL) {
702         /* There's already a dialog box; reactivate it. */
703         reactivate_window(follow_info->follow_save_as_w);
704         return;
705     }
706
707     new_win = gtk_file_selection_new("Ethereal: Save TCP Follow Stream As");
708     follow_info->follow_save_as_w = new_win;
709     gtk_signal_connect(GTK_OBJECT(new_win), "destroy",
710                     GTK_SIGNAL_FUNC(follow_save_as_destroy_cb), follow_info);
711
712     /* Tuck away the follow_info object into the window */
713     gtk_object_set_data(GTK_OBJECT(new_win), E_FOLLOW_INFO_KEY,
714                             follow_info);
715
716     /* If we've opened a file, start out by showing the files in the directory
717        in which that file resided. */
718     if (last_open_dir)
719         gtk_file_selection_complete(GTK_FILE_SELECTION(new_win),
720                                     last_open_dir);
721
722     /* Connect the ok_button to file_save_as_ok_cb function and pass along a
723        pointer to the file selection box widget */
724     ok_bt = GTK_FILE_SELECTION(new_win)->ok_button;
725     gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
726                        (GtkSignalFunc) follow_save_as_ok_cb,
727                        new_win);
728
729     /* Connect the cancel_button to destroy the widget */
730     gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION
731                                          (new_win)->cancel_button),
732                               "clicked",
733                               (GtkSignalFunc) gtk_widget_destroy,
734                               GTK_OBJECT(new_win));
735
736     /* Catch the "key_press_event" signal in the window, so that we can catch
737        the ESC key being pressed and act as if the "Cancel" button had
738        been selected. */
739     dlg_set_cancel(new_win,
740                    GTK_FILE_SELECTION(new_win)->cancel_button);
741
742     gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
743     gtk_widget_show_all(new_win);
744 }
745
746
747 static void
748 follow_save_as_ok_cb(GtkWidget * w, GtkFileSelection * fs)
749 {
750         gchar           *to_name;
751         follow_info_t   *follow_info;
752         FILE            *fh;
753
754         to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
755
756         gtk_widget_hide(GTK_WIDGET(fs));
757         follow_info = gtk_object_get_data(GTK_OBJECT(fs), E_FOLLOW_INFO_KEY);
758         gtk_widget_destroy(GTK_WIDGET(fs));
759
760         fh = fopen(to_name, "wb");
761         if (fh == NULL) {
762                 simple_dialog(ESD_TYPE_WARN, NULL,
763                         file_write_error_message(errno), to_name);
764                 return;
765         }
766
767         follow_read_stream(follow_info, follow_print_text, fh);
768         fclose(fh);
769         g_free(to_name);
770 }
771
772 static void
773 follow_save_as_destroy_cb(GtkWidget * win, gpointer data)
774 {
775         follow_info_t   *follow_info = data;
776
777         /* Note that we no longer have a dialog box. */
778         follow_info->follow_save_as_w = NULL;
779 }