* Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA /* the maximum size of files we will try to make a thumbnail for */ #define PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL 10 * 1024 * 1024 PurpleXfer *selected_xfer; GtkWidget *local_user_desc_label; GtkWidget *local_user_label; GtkWidget *remote_user_desc_label; GtkWidget *remote_user_label; GtkWidget *protocol_label; GtkWidget *filename_label; GtkWidget *localfile_label; GtkWidget *time_elapsed_label; GtkWidget *time_remaining_label; GtkWidget *remove_button; G_DEFINE_TYPE(PidginXferDialog, pidgin_xfer_dialog, GTK_TYPE_DIALOG); gint64 last_updated_time; static PidginXferDialog *xfer_dialog = NULL; /************************************************************************** **************************************************************************/ get_xfer_info_strings(PurpleXfer *xfer, char **kbsec, char **time_elapsed, elapsed = purple_xfer_get_start_time(xfer); now = purple_xfer_get_end_time(xfer); now = g_get_monotonic_time(); kb_sent = purple_xfer_get_bytes_sent(xfer) / 1000.0; kb_rem = purple_xfer_get_bytes_remaining(xfer) / 1000.0; kbps = (elapsed > 0 ? (kb_sent * G_USEC_PER_SEC) / elapsed : 0); *kbsec = g_strdup_printf(_("%.2f KB/s"), kbps); if (time_elapsed != NULL) if (purple_xfer_get_start_time(xfer) > 0) { secs_elapsed = elapsed / G_USEC_PER_SEC; m = (secs_elapsed % 3600) / 60; *time_elapsed = g_strdup_printf("%d:%02d:%02d", h, m, s); *time_elapsed = g_strdup(_("Not started")); if (time_remaining != NULL) { if (purple_xfer_is_completed(xfer)) { *time_remaining = g_strdup(_("Finished")); else if (purple_xfer_is_cancelled(xfer)) { *time_remaining = g_strdup(_("Cancelled")); else if (purple_xfer_get_size(xfer) == 0 || (kb_sent > 0 && kbps < 0.001)) { *time_remaining = g_strdup(_("Unknown")); *time_remaining = g_strdup(_("Waiting for transfer to begin")); secs_remaining = (int)(kb_rem / kbps); h = secs_remaining / 3600; m = (secs_remaining % 3600) / 60; *time_remaining = g_strdup_printf("%d:%02d:%02d", h, m, s); update_title_progress(PidginXferDialog *dialog) int num_active_xfers = 0; guint64 total_bytes_xferred = 0; guint64 total_file_size = 0; valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter); /* Find all active transfers */ gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, xfer = g_value_get_object(&val); if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED) { total_bytes_xferred += purple_xfer_get_bytes_sent(xfer); total_file_size += purple_xfer_get_size(xfer); valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog->model), &iter); if (num_active_xfers > 0) if (total_file_size > 0) { total_pct = 100 * total_bytes_xferred / total_file_size; title = g_strdup_printf(ngettext("File Transfers - %d%% of %d file", "File Transfers - %d%% of %d files", total_pct, num_active_xfers); gtk_window_set_title(GTK_WINDOW(dialog), title); gtk_window_set_title(GTK_WINDOW(dialog), _("File Transfers")); update_detailed_info(PidginXferDialog *dialog, PurpleXfer *xfer) char *kbsec, *time_elapsed, *time_remaining; if (dialog == NULL || xfer == NULL) data = purple_xfer_get_ui_data(xfer); get_xfer_info_strings(xfer, &kbsec, &time_elapsed, &time_remaining); status = g_strdup_printf("%d%% (%" G_GOFFSET_FORMAT " of %" G_GOFFSET_FORMAT " bytes)", (int)(purple_xfer_get_progress(xfer)*100), purple_xfer_get_bytes_sent(xfer), purple_xfer_get_size(xfer)); if (purple_xfer_is_completed(xfer)) { gtk_list_store_set(GTK_LIST_STORE(xfer_dialog->model), &data->iter, if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_label_set_markup(GTK_LABEL(dialog->local_user_desc_label), _("<b>Receiving As:</b>")); gtk_label_set_markup(GTK_LABEL(dialog->remote_user_desc_label), _("<b>Receiving From:</b>")); gtk_label_set_markup(GTK_LABEL(dialog->remote_user_desc_label), _("<b>Sending To:</b>")); gtk_label_set_markup(GTK_LABEL(dialog->local_user_desc_label), _("<b>Sending As:</b>")); gtk_label_set_text(GTK_LABEL(dialog->local_user_label), purple_account_get_username(purple_xfer_get_account(xfer))); gtk_label_set_text(GTK_LABEL(dialog->remote_user_label), purple_xfer_get_remote_user(xfer)); gtk_label_set_text(GTK_LABEL(dialog->protocol_label), purple_account_get_protocol_name(purple_xfer_get_account(xfer))); if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_label_set_text(GTK_LABEL(dialog->filename_label), purple_xfer_get_filename(xfer)); tmp = g_path_get_basename(purple_xfer_get_local_filename(xfer)); utf8 = g_filename_to_utf8(tmp, -1, NULL, NULL, NULL); gtk_label_set_text(GTK_LABEL(dialog->filename_label), utf8); utf8 = g_filename_to_utf8((purple_xfer_get_local_filename(xfer)), -1, NULL, NULL, NULL); gtk_label_set_text(GTK_LABEL(dialog->localfile_label), utf8); gtk_label_set_text(GTK_LABEL(dialog->status_label), status); gtk_label_set_text(GTK_LABEL(dialog->speed_label), kbsec); gtk_label_set_text(GTK_LABEL(dialog->time_elapsed_label), time_elapsed); gtk_label_set_text(GTK_LABEL(dialog->time_remaining_label), gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), purple_xfer_get_progress(xfer)); update_buttons(PidginXferDialog *dialog, PurpleXfer *xfer) if (dialog->selected_xfer == NULL) { gtk_widget_set_sensitive(dialog->expander, FALSE); gtk_widget_set_sensitive(dialog->open_button, FALSE); gtk_widget_set_sensitive(dialog->stop_button, FALSE); gtk_widget_show(dialog->stop_button); gtk_widget_hide(dialog->remove_button); if (dialog->selected_xfer != xfer) if (purple_xfer_is_completed(xfer)) { gtk_widget_hide(dialog->stop_button); gtk_widget_show(dialog->remove_button); if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_widget_set_sensitive(dialog->open_button, TRUE); gtk_widget_set_sensitive(dialog->open_button, FALSE); if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_widget_set_sensitive(dialog->open_button, TRUE); gtk_widget_set_sensitive (dialog->open_button, FALSE); gtk_widget_set_sensitive(dialog->remove_button, TRUE); } else if (purple_xfer_is_cancelled(xfer)) { gtk_widget_hide(dialog->stop_button); gtk_widget_show(dialog->remove_button); gtk_widget_set_sensitive(dialog->open_button, FALSE); gtk_widget_set_sensitive(dialog->remove_button, TRUE); gtk_widget_show(dialog->stop_button); gtk_widget_hide(dialog->remove_button); gtk_widget_set_sensitive(dialog->open_button, FALSE); gtk_widget_set_sensitive(dialog->stop_button, TRUE); ensure_row_selected(PidginXferDialog *dialog) GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->tree)); if (gtk_tree_selection_get_selected(selection, NULL, &iter)) if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter)) gtk_tree_selection_select_iter(selection, &iter); /************************************************************************** **************************************************************************/ delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d) PidginXferDialog *dialog; dialog = (PidginXferDialog *)d; pidgin_xfer_dialog_hide(dialog); toggle_keep_open_cb(GtkWidget *w, G_GNUC_UNUSED gpointer data) PIDGIN_PREFS_ROOT "/filetransfer/keep_open", !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))); toggle_clear_finished_cb(GtkWidget *w, G_GNUC_UNUSED gpointer data) PIDGIN_PREFS_ROOT "/filetransfer/clear_finished", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))); selection_changed_cb(GtkTreeSelection *selection, PidginXferDialog *dialog) if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { gtk_widget_set_sensitive(dialog->expander, TRUE); gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, xfer = g_value_get_object(&val); update_detailed_info(dialog, xfer); dialog->selected_xfer = xfer; gtk_expander_set_expanded(GTK_EXPANDER(dialog->expander), gtk_widget_set_sensitive(dialog->expander, FALSE); dialog->selected_xfer = NULL; update_buttons(dialog, xfer); open_button_cb(GtkButton *button, PidginXferDialog *dialog) wchar_t *wc_filename = g_utf8_to_utf16( purple_xfer_get_local_filename( code = (int) ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL, if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC) purple_notify_error(dialog, NULL, _("There is no application configured to open this type of file."), purple_notify_error(dialog, NULL, _("An error occurred while opening the file."), NULL, NULL); purple_debug_warning("xfer", "filename: %s; code: %d\n", purple_xfer_get_local_filename(dialog->selected_xfer), code); const char *filename = purple_xfer_get_local_filename(dialog->selected_xfer); if (purple_running_gnome()) char *escaped = g_shell_quote(filename); command = g_strdup_printf("gnome-open %s", escaped); else if (purple_running_kde()) char *escaped = g_shell_quote(filename); if (purple_str_has_suffix(filename, ".desktop")) command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped); command = g_strdup_printf("kfmclient openURL %s", escaped); gchar *uri = g_strdup_printf("file://%s", filename); purple_notify_uri(NULL, uri); if (purple_program_is_valid(command)) if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error)) char *tmp = g_strdup_printf(_("Error launching %s: %s"), purple_xfer_get_local_filename(dialog->selected_xfer), purple_notify_error(dialog, NULL, _("Unable to open file."), tmp, NULL); char *primary = g_strdup_printf(_("Error running %s"), command); char *secondary = g_strdup_printf(_("Process returned error code %d"), purple_notify_error(dialog, NULL, primary, secondary, NULL); remove_button_cb(GtkButton *button, PidginXferDialog *dialog) pidgin_xfer_dialog_remove_xfer(dialog, dialog->selected_xfer); stop_button_cb(GtkButton *button, PidginXferDialog *dialog) purple_xfer_cancel_local(dialog->selected_xfer); close_button_cb(GtkButton *button, PidginXferDialog *dialog) pidgin_xfer_dialog_hide(dialog); /************************************************************************** * Dialog Building Functions **************************************************************************/ pidgin_xfer_dialog_new(void) return PIDGIN_XFER_DIALOG(g_object_new(PIDGIN_TYPE_XFER_DIALOG, NULL)); pidgin_xfer_dialog_class_init(PidginXferDialogClass *klass) GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); gtk_widget_class_set_template_from_resource( widget_class, "/im/pidgin/Pidgin/Xfer/xfer.ui"); gtk_widget_class_bind_template_callback(widget_class, delete_win_cb); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_callback(widget_class, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_callback(widget_class, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_callback(widget_class, toggle_clear_finished_cb); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, gtk_widget_class_bind_template_callback(widget_class, open_button_cb); gtk_widget_class_bind_template_callback(widget_class, remove_button_cb); gtk_widget_class_bind_template_callback(widget_class, stop_button_cb); gtk_widget_class_bind_template_callback(widget_class, close_button_cb); pidgin_xfer_dialog_init(PidginXferDialog *dialog) gtk_widget_init_template(GTK_WIDGET(dialog)); /* "Close this window when all transfers finish" */ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(dialog->keep_open), !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/keep_open")); /* "Clear finished transfers" */ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(dialog->auto_clear), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished")); g_signal_connect(G_OBJECT(dialog), "show", G_CALLBACK(winpidgin_ensure_onscreen), NULL); pidgin_xfer_dialog_destroy(PidginXferDialog *dialog) g_return_if_fail(dialog != NULL); purple_notify_close_with_handle(dialog); gtk_widget_destroy(GTK_WIDGET(dialog)); pidgin_xfer_dialog_show(PidginXferDialog *dialog) tmp = pidgin_get_xfer_dialog(); tmp = pidgin_xfer_dialog_new(); pidgin_set_xfer_dialog(tmp); gtk_widget_show(GTK_WIDGET(tmp)); gtk_window_present(GTK_WINDOW(dialog)); pidgin_xfer_dialog_hide(PidginXferDialog *dialog) g_return_if_fail(dialog != NULL); purple_notify_close_with_handle(dialog); gtk_widget_hide(GTK_WIDGET(dialog)); pidgin_xfer_dialog_add_xfer(PidginXferDialog *dialog, PurpleXfer *xfer) char *size_str, *remaining_str; g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); data = purple_xfer_get_ui_data(xfer); pidgin_xfer_dialog_show(dialog); data->last_updated_time = 0; type = purple_xfer_get_xfer_type(xfer); size_str = g_format_size(purple_xfer_get_size(xfer)); remaining_str = g_format_size(purple_xfer_get_bytes_remaining(xfer)); icon_name = (type == PURPLE_XFER_TYPE_RECEIVE ? "go-down" : "go-up"); gtk_list_store_append(dialog->model, &data->iter); lfilename = g_path_get_basename(purple_xfer_get_local_filename(xfer)); utf8 = g_filename_to_utf8(lfilename, -1, NULL, NULL, NULL); gtk_list_store_set(dialog->model, &data->iter, COLUMN_STATUS, icon_name, COLUMN_PROGRESS, 0, COLUMN_FILENAME, (type == PURPLE_XFER_TYPE_RECEIVE) ? purple_xfer_get_filename(xfer) COLUMN_SIZE, size_str, COLUMN_REMAINING, _("Waiting for transfer to begin"), COLUMN_XFER, gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dialog->tree)); ensure_row_selected(dialog); update_title_progress(dialog); pidgin_xfer_dialog_remove_xfer(PidginXferDialog *dialog, g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); data = purple_xfer_get_ui_data(xfer); gtk_list_store_remove(GTK_LIST_STORE(dialog->model), &data->iter); ensure_row_selected(dialog); update_title_progress(dialog); pidgin_xfer_dialog_cancel_xfer(PidginXferDialog *dialog, g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); data = purple_xfer_get_ui_data(xfer); if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL && gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(dialog->auto_clear))) { pidgin_xfer_dialog_remove_xfer(dialog, xfer); data = purple_xfer_get_ui_data(xfer); update_detailed_info(dialog, xfer); update_title_progress(dialog); if (purple_xfer_is_cancelled(xfer)) gtk_list_store_set(dialog->model, &data->iter, COLUMN_STATUS, "dialog-error", COLUMN_REMAINING, status, update_buttons(dialog, xfer); pidgin_xfer_dialog_update_xfer(PidginXferDialog *dialog, char *size_str, *remaining_str; g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); if ((data = purple_xfer_get_ui_data(xfer)) == NULL) if (data->in_list == FALSE) current_time = g_get_monotonic_time(); if (((current_time - data->last_updated_time) < G_USEC_PER_SEC) && (!purple_xfer_is_completed(xfer))) /* Don't update the window more than once per second */ data->last_updated_time = current_time; size_str = g_format_size(purple_xfer_get_size(xfer)); remaining_str = g_format_size(purple_xfer_get_bytes_remaining(xfer)); gtk_list_store_set(xfer_dialog->model, &data->iter, COLUMN_PROGRESS, (gint)(purple_xfer_get_progress(xfer) * 100), COLUMN_REMAINING, remaining_str, if (purple_xfer_is_completed(xfer)) gtk_list_store_set(GTK_LIST_STORE(xfer_dialog->model), &data->iter, COLUMN_REMAINING, _("Finished"), update_title_progress(dialog); if (xfer == dialog->selected_xfer) update_detailed_info(xfer_dialog, xfer); if (purple_xfer_is_completed(xfer) && gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(dialog->auto_clear))) { pidgin_xfer_dialog_remove_xfer(dialog, xfer); update_buttons(dialog, xfer); * If all transfers are finished, and the pref is set, then * close the dialog. Otherwise just exit this function. if (!gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(dialog->keep_open))) { valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter); gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, next = g_value_get_object(&val); if (!purple_xfer_is_completed(next)) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog->model), &iter); /* If we got to this point then we know everything is finished */ pidgin_xfer_dialog_hide(dialog); /************************************************************************** **************************************************************************/ pidgin_xfer_new_xfer(PurpleXfer *xfer) /* This is where we're setting xfer's "ui_data" for the first time. */ data = g_new0(PidginXferUiData, 1); purple_xfer_set_ui_data(xfer, data); pidgin_xfer_destroy(PurpleXfer *xfer) data = purple_xfer_get_ui_data(xfer); purple_xfer_set_ui_data(xfer, NULL); pidgin_xfer_add_xfer(PurpleXfer *xfer) xfer_dialog = pidgin_xfer_dialog_new(); pidgin_xfer_dialog_add_xfer(xfer_dialog, xfer); pidgin_xfer_update_progress(PurpleXfer *xfer, double percent) pidgin_xfer_dialog_update_xfer(xfer_dialog, xfer); pidgin_xfer_cancel_local(PurpleXfer *xfer) pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer); pidgin_xfer_cancel_remote(PurpleXfer *xfer) pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer); pidgin_xfer_add_thumbnail(PurpleXfer *xfer, const gchar *formats) purple_debug_info("xfer", "creating thumbnail for transfer\n"); if (purple_xfer_get_size(xfer) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL) { pidgin_pixbuf_new_from_file_at_size( purple_xfer_get_local_filename(xfer), 128, 128); gchar **formats_split = g_strsplit(formats, ",", 0); char *option_keys[2] = {NULL, NULL}; char *option_values[2] = {NULL, NULL}; for (i = 0; formats_split[i]; i++) { if (purple_strequal(formats_split[i], "jpeg")) { purple_debug_info("xfer", "creating JPEG thumbnail\n"); option_keys[0] = "quality"; } else if (purple_strequal(formats_split[i], "png")) { purple_debug_info("xfer", "creating PNG thumbnail\n"); option_keys[0] = "compression"; /* Try the first format given by the protocol without options */ purple_debug_info("xfer", "creating thumbnail of format %s as demanded by protocol\n", format = formats_split[0]; gdk_pixbuf_save_to_bufferv(thumbnail, &buffer, &size, format, option_keys, option_values, NULL); gchar *mimetype = g_strdup_printf("image/%s", format); purple_debug_info("xfer", "created thumbnail of %" G_GSIZE_FORMAT " bytes\n", purple_xfer_set_thumbnail(xfer, buffer, size, mimetype); g_object_unref(thumbnail); g_strfreev(formats_split); static PurpleXferUiOps ops = pidgin_xfer_update_progress, pidgin_xfer_cancel_local, pidgin_xfer_cancel_remote, pidgin_xfer_add_thumbnail /************************************************************************** **************************************************************************/ purple_prefs_add_none(PIDGIN_PREFS_ROOT "/filetransfer"); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished", TRUE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/filetransfer/keep_open", FALSE); pidgin_xfers_uninit(void) pidgin_xfer_dialog_destroy(xfer_dialog); pidgin_set_xfer_dialog(PidginXferDialog *dialog) pidgin_get_xfer_dialog(void) pidgin_xfers_get_ui_ops(void)