/* dnd.c * Drag and Drop * * $Id$ * * Ethereal - Network traffic analyzer * By Gerald Combs * 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 #include "globals.h" #include "gtkglobals.h" #include "util.h" #include "file_dlg.h" #include "../menu.h" #include "compat_macros.h" #include "file.h" #include "simple_dialog.h" #include "main.h" #ifdef HAVE_LIBPCAP #include "capture.h" #endif #include #include #include enum { DND_TARGET_STRING, DND_TARGET_ROOTWIN, DND_TARGET_URL }; /* convert drag and drop URI to a local filename */ static gchar * dnd_uri2filename(gchar *cf_name) { gchar *src, *dest; gint ret; guint i; gchar esc[3]; /* Remove URI header. * we have to remove the prefix to get a valid filename. */ #ifdef _WIN32 /* * On win32 (at least WinXP), this prefix looks like (UNC): * file:////servername/sharename/dir1/dir2/capture-file.cap * or (local filename): * file:///d:/dir1/dir2/capture-file.cap */ if (strncmp("file:////", cf_name, 9) == 0) { /* win32 UNC: now becoming: //servername/sharename/dir1/dir2/capture-file.cap */ cf_name += 7; } else if (strncmp("file:///", cf_name, 8) == 0) { /* win32 local: now becoming: d:/dir1/dir2/capture-file.cap */ cf_name += 8; } #else /* * On UNIX (at least KDE 3.0 Konqueror), this prefix looks like: * file:/dir1/dir2/capture-file.cap * * On UNIX (at least GNOME Nautilus 2.8.2), this prefix looks like: * file:///dir1/dir2/capture-file.cap */ if (strncmp("file:", cf_name, 5) == 0) { /* now becoming: /dir1/dir2/capture-file.cap or ///dir1/dir2/capture-file.cap */ cf_name += 5; /* shorten //////thing to /thing */ for(; cf_name[1] == '/'; ++cf_name); } #endif /* * unescape the escaped URI characters (spaces, ...) * * we have to replace escaped chars to their equivalents, * e.g. %20 (always a two digit hexstring) -> ' ' * the percent character '%' is escaped be a double one "%%" * * we do this conversation "in place" as the result is always * equal or smaller in size. */ src = cf_name; dest = cf_name; while (*src) { if (*src == '%') { src++; if (*src == '%') { /* this is an escaped '%' char (was: "%%") */ *dest = *src; src++; dest++; } else { /* convert escaped hexnumber to unscaped character */ esc[0] = src[0]; esc[1] = src[1]; esc[2] = '\0'; ret = sscanf(esc, "%x", &i); if (ret == 1) { src+=2; *dest = (gchar) i; dest++; } else { /* somethings wrong, just jump over that char * this will result in a wrong string, but we might get * user feedback and can fix it later ;-) */ src++; } } } else { *dest = *src; src++; dest++; } } *dest = '\0'; return cf_name; } static void dnd_merge_files(int in_file_count, char **in_filenames) { char *tmpname; cf_status_t merge_status; int err; /* merge the files in chonological order */ tmpname = NULL; merge_status = cf_merge_files(&tmpname, in_file_count, in_filenames, WTAP_FILE_PCAP, FALSE); if (merge_status != CF_OK) { /* merge failed */ g_free(tmpname); return; } cf_close(&cfile); /* Try to open the merged capture file. */ if (cf_open(&cfile, tmpname, TRUE /* temporary file */, &err) != CF_OK) { /* We couldn't open it; don't dismiss the open dialog box, just leave it around so that the user can, after they dismiss the alert box popped up for the open error, try again. */ g_free(tmpname); return; } g_free(tmpname); switch (cf_read(&cfile)) { case CF_READ_OK: case CF_READ_ERROR: /* Just because we got an error, that doesn't mean we were unable to read any of the file; we handle what we could get from the file. */ break; case CF_READ_ABORTED: /* The user bailed out of re-reading the capture file; the capture file has been closed - just free the capture file name string and return (without changing the last containing directory). */ return; } gtk_widget_grab_focus(packet_list); } /* open/merge the dnd file */ void dnd_open_file_cmd(gchar *cf_names_freeme) { int err; gchar *cf_name; int in_files; gpointer dialog; GString *dialog_text; int files_work; char **in_filenames; /* DND_TARGET_URL on Win32: * The cf_name_freeme is a single string, containing one or more URI's, * seperated by CR/NL chars. The length of the whole field can be found * in the selection_data->length field. If it contains one file, simply open it, * If it contains more than one file, ask to merge these files. */ /* count the number of input files */ cf_name = cf_names_freeme; for(in_files = 0; (cf_name = strstr(cf_name, "\r\n")) != NULL; ) { cf_name += 2; in_files++; } in_filenames = g_malloc(sizeof(char*) * in_files); /* store the starts of the file entries in a gchar array */ cf_name = cf_names_freeme; in_filenames[0] = cf_name; for(files_work = 1; (cf_name = strstr(cf_name, "\r\n")) != NULL && files_work < in_files; ) { cf_name += 2; in_filenames[files_work] = cf_name; files_work++; } /* replace trailing CR NL simply with zeroes (in place), so we get valid terminated strings */ cf_name = cf_names_freeme; g_strdelimit(cf_name, "\r\n", '\0'); /* convert all filenames from URI to local filename (in place) */ for(files_work = 0; files_work < in_files; files_work++) { in_filenames[files_work] = dnd_uri2filename(in_filenames[files_work]); } switch(in_files) { case(0): /* shouldn't happen */ break; case(1): /* open and read the capture file (this will close an existing file) */ if (cf_open(&cfile, in_filenames[0], FALSE, &err) == CF_OK) { /* XXX - add this to the menu if the read fails? */ cf_read(&cfile); add_menu_recent_capture_file(in_filenames[0]); } else { /* the capture file couldn't be read (doesn't exist, file format unknown, ...) */ } break; default: /* build and show the info dialog */ dialog_text = g_string_sized_new(200); g_string_append(dialog_text, PRIMARY_TEXT_START "Merging the following files:" PRIMARY_TEXT_END "\n\n"); for(files_work = 0; files_work < in_files; files_work++) { g_string_append(dialog_text, in_filenames[files_work]); g_string_append(dialog_text, "\n"); } g_string_append(dialog_text, "\nThe packets in these files will be merged chronologically into a new temporary file."); dialog = simple_dialog(ESD_TYPE_CONFIRMATION, ESD_BTN_OK, dialog_text->str); g_string_free(dialog_text, TRUE); /* actually merge the files now */ dnd_merge_files(in_files, in_filenames); } g_free(in_filenames); g_free(cf_names_freeme); } /* ask the user to save current unsaved file, before opening the dnd file */ static void dnd_save_file_answered_cb(gpointer dialog _U_, gint btn, gpointer data) { switch(btn) { case(ESD_BTN_SAVE): /* save file first */ file_save_as_cmd(after_save_open_dnd_file, data); break; case(ESD_BTN_DONT_SAVE): cf_close(&cfile); dnd_open_file_cmd(data); break; case(ESD_BTN_CANCEL): break; default: g_assert_not_reached(); } } /* we have received some drag and drop data */ /* (as we only registered to "text/uri-list", we will only get a file list here) */ static void dnd_data_received(GtkWidget *widget _U_, GdkDragContext *dc _U_, gint x _U_, gint y _U_, GtkSelectionData *selection_data, guint info, guint t _U_, gpointer data _U_) { gpointer dialog; gchar *cf_names_freeme; if (info == DND_TARGET_URL) { /* Usually we block incoming events by disabling the corresponding menu/toolbar items. * This is the only place where an incoming event won't be blocked in such a way, * so we have to take care of NOT loading a new file while a different process * (e.g. capture/load/...) is still in progress. */ #ifdef HAVE_LIBPCAP /* if a capture is running, do nothing but warn the user */ if((capture_opts->state != CAPTURE_STOPPED)) { dialog = simple_dialog(ESD_TYPE_CONFIRMATION, ESD_BTN_OK, PRIMARY_TEXT_START "Drag and Drop currently not possible!" PRIMARY_TEXT_END "\n\n" "Dropping a file isn't possible while a capture is in progress."); return; } #endif /* if another file read is still in progress, do nothing but warn the user */ if((cfile.state == FILE_READ_IN_PROGRESS)) { dialog = simple_dialog(ESD_TYPE_CONFIRMATION, ESD_BTN_OK, PRIMARY_TEXT_START "Drag and Drop currently not possible!" PRIMARY_TEXT_END "\n\n" "Dropping a file isn't possible while loading another capture file."); return; } /* the selection_data will soon be gone, make a copy first */ /* the data string is not zero terminated -> make a zero terminated "copy" of it */ cf_names_freeme = g_malloc(selection_data->length + 1); memcpy(cf_names_freeme, selection_data->data, selection_data->length); cf_names_freeme[selection_data->length] = '\0'; /* ask the user to save it's current capture file first */ if((cfile.state != FILE_CLOSED) && !cfile.user_saved && prefs.gui_ask_unsaved) { /* user didn't saved his current file, ask him */ dialog = simple_dialog(ESD_TYPE_CONFIRMATION, ESD_BTNS_SAVE_DONTSAVE_CANCEL, PRIMARY_TEXT_START "Save capture file before opening a new one?" PRIMARY_TEXT_END "\n\n" "If you open a new capture file without saving, your current capture data will be discarded."); simple_dialog_set_cb(dialog, dnd_save_file_answered_cb, cf_names_freeme ); } else { /* unchanged file */ dnd_open_file_cmd( cf_names_freeme ); } } } /* init the drag and drop functionality */ void dnd_init(GtkWidget *w) { /* we are only interested in the URI list containing filenames */ static GtkTargetEntry target_entry[] = { /*{"STRING", 0, DND_TARGET_STRING},*/ /*{"text/plain", 0, DND_TARGET_STRING},*/ {"text/uri-list", 0, DND_TARGET_URL} }; /* set this window as a dnd destination */ gtk_drag_dest_set( w, GTK_DEST_DEFAULT_ALL, target_entry, sizeof(target_entry) / sizeof(GtkTargetEntry), (GdkDragAction)(GDK_ACTION_MOVE | GDK_ACTION_COPY) ); /* get notified, if some dnd coming in */ gtk_signal_connect(GTK_OBJECT(w), "drag_data_received", GTK_SIGNAL_FUNC(dnd_data_received), NULL); }