/* pidgin * * 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 * source distribution. * * 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 */ #include #include #include #include "gtkxfer.h" #include "gtkutils.h" #include "pidgincore.h" #ifdef _WIN32 # include #endif /* the maximum size of files we will try to make a thumbnail for */ #define PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL 10 * 1024 * 1024 struct _PidginXferDialog { GtkDialog parent; GtkWidget *keep_open; GtkWidget *auto_clear; PurpleXfer *selected_xfer; GtkWidget *tree; GtkListStore *model; GtkWidget *expander; 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 *status_label; GtkWidget *speed_label; GtkWidget *time_elapsed_label; GtkWidget *time_remaining_label; GtkWidget *progress; /* Buttons */ GtkWidget *open_button; GtkWidget *remove_button; GtkWidget *stop_button; GtkWidget *close_button; }; G_DEFINE_TYPE(PidginXferDialog, pidgin_xfer_dialog, GTK_TYPE_DIALOG); #define UI_DATA "pidgin-ui-data" typedef struct { GtkTreeIter iter; gint64 last_updated_time; } PidginXferUiData; static PidginXferDialog *xfer_dialog = NULL; enum { COLUMN_STATUS = 0, COLUMN_PROGRESS, COLUMN_FILENAME, COLUMN_SIZE, COLUMN_REMAINING, COLUMN_XFER, NUM_COLUMNS }; /************************************************************************** * Utility Functions **************************************************************************/ static void get_xfer_info_strings(PurpleXfer *xfer, char **kbsec, char **time_elapsed, char **time_remaining) { double kb_sent, kb_rem; double kbps = 0.0; gint64 now; gint64 elapsed = 0; elapsed = purple_xfer_get_start_time(xfer); if (elapsed > 0) { now = purple_xfer_get_end_time(xfer); if (now == 0) { now = g_get_monotonic_time(); } elapsed = now - elapsed; } 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); if (kbsec != NULL) { *kbsec = g_strdup_printf(_("%.2f KB/s"), kbps); } if (time_elapsed != NULL) { int h, m, s; int secs_elapsed; if (purple_xfer_get_start_time(xfer) > 0) { secs_elapsed = elapsed / G_USEC_PER_SEC; h = secs_elapsed / 3600; m = (secs_elapsed % 3600) / 60; s = secs_elapsed % 60; *time_elapsed = g_strdup_printf("%d:%02d:%02d", h, m, s); } else { *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")); } else if (kb_sent <= 0) { *time_remaining = g_strdup(_("Waiting for transfer to begin")); } else { int h, m, s; int secs_remaining; secs_remaining = (int)(kb_rem / kbps); h = secs_remaining / 3600; m = (secs_remaining % 3600) / 60; s = secs_remaining % 60; *time_remaining = g_strdup_printf("%d:%02d:%02d", h, m, s); } } } static void update_title_progress(PidginXferDialog *dialog) { gboolean valid; GtkTreeIter iter; 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 */ while (valid) { GValue val; PurpleXfer *xfer = NULL; val.g_type = 0; gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, COLUMN_XFER, &val); xfer = g_value_get_object(&val); if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED) { num_active_xfers++; 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); } /* Update the title */ if (num_active_xfers > 0) { gchar *title; int total_pct = 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", num_active_xfers), total_pct, num_active_xfers); gtk_window_set_title(GTK_WINDOW(dialog), title); g_free(title); } else { gtk_window_set_title(GTK_WINDOW(dialog), _("File Transfers")); } } static void update_detailed_info(PidginXferDialog *dialog, PurpleXfer *xfer) { PurpleAccount *account = NULL; PurpleContactInfo *info = NULL; PidginXferUiData *data; char *kbsec, *time_elapsed, *time_remaining; char *status, *utf8; if (dialog == NULL || xfer == NULL) return; data = g_object_get_data(G_OBJECT(xfer), UI_DATA); 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, COLUMN_STATUS, NULL, -1); } if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_label_set_markup(GTK_LABEL(dialog->local_user_desc_label), _("Receiving As:")); gtk_label_set_markup(GTK_LABEL(dialog->remote_user_desc_label), _("Receiving From:")); } else { gtk_label_set_markup(GTK_LABEL(dialog->remote_user_desc_label), _("Sending To:")); gtk_label_set_markup(GTK_LABEL(dialog->local_user_desc_label), _("Sending As:")); } account = purple_xfer_get_account(xfer); info = PURPLE_CONTACT_INFO(account); gtk_label_set_text(GTK_LABEL(dialog->local_user_label), purple_contact_info_get_username(info)); 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(account)); 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)); } else { char *tmp; tmp = g_path_get_basename(purple_xfer_get_local_filename(xfer)); utf8 = g_filename_to_utf8(tmp, -1, NULL, NULL, NULL); g_free(tmp); gtk_label_set_text(GTK_LABEL(dialog->filename_label), utf8); g_free(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); g_free(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), time_remaining); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), purple_xfer_get_progress(xfer)); g_free(kbsec); g_free(time_elapsed); g_free(time_remaining); g_free(status); } static void 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_set_visible(dialog->stop_button, TRUE); gtk_widget_set_visible(dialog->remove_button, FALSE); return; } if (dialog->selected_xfer != xfer) return; if (purple_xfer_is_completed(xfer)) { gtk_widget_set_visible(dialog->stop_button, FALSE); gtk_widget_set_visible(dialog->remove_button, TRUE); #ifdef _WIN32 /* If using Win32... */ if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_widget_set_sensitive(dialog->open_button, TRUE); } else { gtk_widget_set_sensitive(dialog->open_button, FALSE); } #else if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) { gtk_widget_set_sensitive(dialog->open_button, TRUE); } else { gtk_widget_set_sensitive (dialog->open_button, FALSE); } #endif gtk_widget_set_sensitive(dialog->remove_button, TRUE); } else if (purple_xfer_is_cancelled(xfer)) { gtk_widget_set_visible(dialog->stop_button, FALSE); gtk_widget_set_visible(dialog->remove_button, TRUE); gtk_widget_set_sensitive(dialog->open_button, FALSE); gtk_widget_set_sensitive(dialog->remove_button, TRUE); } else { gtk_widget_set_visible(dialog->stop_button, TRUE); gtk_widget_set_visible(dialog->remove_button, FALSE); gtk_widget_set_sensitive(dialog->open_button, FALSE); gtk_widget_set_sensitive(dialog->stop_button, TRUE); } } static void ensure_row_selected(PidginXferDialog *dialog) { GtkTreeIter iter; GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->tree)); if (gtk_tree_selection_get_selected(selection, NULL, &iter)) return; if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter)) gtk_tree_selection_select_iter(selection, &iter); } /************************************************************************** * Callbacks **************************************************************************/ static gboolean close_request_cb(G_GNUC_UNUSED GtkWidget *w, gpointer d) { PidginXferDialog *dialog = (PidginXferDialog *)d; pidgin_xfer_dialog_hide(dialog); return TRUE; } static void toggle_keep_open_cb(GtkWidget *w, G_GNUC_UNUSED gpointer data) { purple_prefs_set_bool( PIDGIN_PREFS_ROOT "/filetransfer/keep_open", !gtk_check_button_get_active(GTK_CHECK_BUTTON(w))); } static void toggle_clear_finished_cb(GtkWidget *w, G_GNUC_UNUSED gpointer data) { purple_prefs_set_bool( PIDGIN_PREFS_ROOT "/filetransfer/clear_finished", gtk_check_button_get_active(GTK_CHECK_BUTTON(w))); } static void selection_changed_cb(GtkTreeSelection *selection, PidginXferDialog *dialog) { GtkTreeIter iter; PurpleXfer *xfer = NULL; if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { GValue val; gtk_widget_set_sensitive(dialog->expander, TRUE); val.g_type = 0; gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, COLUMN_XFER, &val); xfer = g_value_get_object(&val); update_detailed_info(dialog, xfer); dialog->selected_xfer = xfer; } else { gtk_expander_set_expanded(GTK_EXPANDER(dialog->expander), FALSE); gtk_widget_set_sensitive(dialog->expander, FALSE); dialog->selected_xfer = NULL; } update_buttons(dialog, xfer); } static void open_button_cb(G_GNUC_UNUSED GtkButton *button, PidginXferDialog *dialog) { gchar *uri = NULL; uri = g_strdup_printf("file://%s", purple_xfer_get_local_filename(dialog->selected_xfer)); gtk_show_uri(GTK_WINDOW(dialog), uri, GDK_CURRENT_TIME); g_free(uri); } static void remove_button_cb(G_GNUC_UNUSED GtkButton *button, PidginXferDialog *dialog) { pidgin_xfer_dialog_remove_xfer(dialog, dialog->selected_xfer); } static void stop_button_cb(G_GNUC_UNUSED GtkButton *button, PidginXferDialog *dialog) { purple_xfer_cancel_local(dialog->selected_xfer); } static void close_button_cb(G_GNUC_UNUSED GtkButton *button, PidginXferDialog *dialog) { pidgin_xfer_dialog_hide(dialog); } /************************************************************************** * Dialog Building Functions **************************************************************************/ PidginXferDialog * pidgin_xfer_dialog_new(void) { return PIDGIN_XFER_DIALOG(g_object_new(PIDGIN_TYPE_XFER_DIALOG, NULL)); } static void 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/Pidgin3/Xfer/xfer.ui"); gtk_widget_class_bind_template_callback(widget_class, close_request_cb); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, tree); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, model); gtk_widget_class_bind_template_callback(widget_class, selection_changed_cb); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, keep_open); gtk_widget_class_bind_template_callback(widget_class, toggle_keep_open_cb); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, auto_clear); gtk_widget_class_bind_template_callback(widget_class, toggle_clear_finished_cb); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, expander); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, local_user_desc_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, local_user_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, remote_user_desc_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, remote_user_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, protocol_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, filename_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, localfile_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, status_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, speed_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, time_elapsed_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, time_remaining_label); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, progress); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, open_button); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, remove_button); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, stop_button); gtk_widget_class_bind_template_child(widget_class, PidginXferDialog, close_button); 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); } static void pidgin_xfer_dialog_init(PidginXferDialog *dialog) { gtk_widget_init_template(GTK_WIDGET(dialog)); /* "Close this window when all transfers finish" */ gtk_check_button_set_active( GTK_CHECK_BUTTON(dialog->keep_open), !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/keep_open")); /* "Clear finished transfers" */ gtk_check_button_set_active( GTK_CHECK_BUTTON(dialog->auto_clear), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished")); } void pidgin_xfer_dialog_destroy(PidginXferDialog *dialog) { g_return_if_fail(dialog != NULL); purple_notify_close_with_handle(dialog); gtk_window_destroy(GTK_WINDOW(dialog)); } void pidgin_xfer_dialog_show(PidginXferDialog *dialog) { PidginXferDialog *tmp; if (dialog == NULL) { tmp = pidgin_get_xfer_dialog(); if (tmp == NULL) { tmp = pidgin_xfer_dialog_new(); pidgin_set_xfer_dialog(tmp); } gtk_widget_set_visible(GTK_WIDGET(tmp), TRUE); } else { gtk_window_present(GTK_WINDOW(dialog)); } } void pidgin_xfer_dialog_hide(PidginXferDialog *dialog) { g_return_if_fail(dialog != NULL); purple_notify_close_with_handle(dialog); gtk_widget_set_visible(GTK_WIDGET(dialog), FALSE); } void pidgin_xfer_dialog_add_xfer(PidginXferDialog *dialog, PurpleXfer *xfer) { PidginXferUiData *data; PurpleXferType type; const gchar *icon_name; char *size_str, *remaining_str; char *lfilename, *utf8; g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); g_object_ref(xfer); data = g_object_get_data(G_OBJECT(xfer), UI_DATA); 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); g_free(lfilename); lfilename = utf8; 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) : lfilename, COLUMN_SIZE, size_str, COLUMN_REMAINING, _("Waiting for transfer to begin"), COLUMN_XFER, xfer, -1); g_free(lfilename); gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dialog->tree)); g_free(size_str); g_free(remaining_str); ensure_row_selected(dialog); update_title_progress(dialog); } void pidgin_xfer_dialog_remove_xfer(PidginXferDialog *dialog, PurpleXfer *xfer) { PidginXferUiData *data; g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); data = g_object_get_data(G_OBJECT(xfer), UI_DATA); if (data == NULL) return; if (!purple_xfer_get_visible(xfer)) { return; } purple_xfer_set_visible(xfer, FALSE); gtk_list_store_remove(GTK_LIST_STORE(dialog->model), &data->iter); ensure_row_selected(dialog); update_title_progress(dialog); g_object_unref(xfer); } void pidgin_xfer_dialog_cancel_xfer(PidginXferDialog *dialog, PurpleXfer *xfer) { PidginXferUiData *data; const gchar *status; g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); data = g_object_get_data(G_OBJECT(xfer), UI_DATA); if (data == NULL) return; if (!purple_xfer_get_visible(xfer)) { return; } if(purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL && gtk_check_button_get_active(GTK_CHECK_BUTTON(dialog->auto_clear))) { pidgin_xfer_dialog_remove_xfer(dialog, xfer); return; } data = g_object_get_data(G_OBJECT(xfer), UI_DATA); update_detailed_info(dialog, xfer); update_title_progress(dialog); if (purple_xfer_is_cancelled(xfer)) status = _("Cancelled"); else status = _("Failed"); gtk_list_store_set(dialog->model, &data->iter, COLUMN_STATUS, "dialog-error", COLUMN_REMAINING, status, -1); update_buttons(dialog, xfer); } void pidgin_xfer_dialog_update_xfer(PidginXferDialog *dialog, PurpleXfer *xfer) { PidginXferUiData *data; char *size_str, *remaining_str; gint64 current_time; GtkTreeIter iter; gboolean valid; g_return_if_fail(dialog != NULL); g_return_if_fail(xfer != NULL); data = g_object_get_data(G_OBJECT(xfer), UI_DATA); if (data == NULL || !purple_xfer_get_visible(xfer)) { return; } 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 */ return; } 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_SIZE, size_str, COLUMN_REMAINING, remaining_str, -1); g_free(size_str); g_free(remaining_str); if (purple_xfer_is_completed(xfer)) { gtk_list_store_set(GTK_LIST_STORE(xfer_dialog->model), &data->iter, COLUMN_STATUS, NULL, COLUMN_REMAINING, _("Finished"), -1); } update_title_progress(dialog); if (xfer == dialog->selected_xfer) update_detailed_info(xfer_dialog, xfer); if(purple_xfer_is_completed(xfer) && gtk_check_button_get_active(GTK_CHECK_BUTTON(dialog->auto_clear))) { pidgin_xfer_dialog_remove_xfer(dialog, xfer); } else { 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_check_button_get_active(GTK_CHECK_BUTTON(dialog->keep_open))) { return; } valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter); while (valid) { GValue val; PurpleXfer *next; val.g_type = 0; gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, COLUMN_XFER, &val); next = g_value_get_object(&val); if (!purple_xfer_is_completed(next)) return; 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); } /************************************************************************** * File Transfer UI Ops **************************************************************************/ static void pidgin_xfer_add_thumbnail(PurpleXfer *xfer, const gchar *formats, G_GNUC_UNUSED gpointer data) { purple_debug_info("xfer", "creating thumbnail for transfer\n"); if (purple_xfer_get_size(xfer) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL) { GdkPixbuf *thumbnail = purple_gdk_pixbuf_new_from_file_at_size( purple_xfer_get_local_filename(xfer), 128, 128); if (thumbnail) { gchar **formats_split = g_strsplit(formats, ",", 0); gchar *buffer = NULL; gsize size; char *option_keys[2] = {NULL, NULL}; char *option_values[2] = {NULL, NULL}; int i; gchar *format = 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"; option_values[0] = "90"; format = "jpeg"; break; } else if (purple_strequal(formats_split[i], "png")) { purple_debug_info("xfer", "creating PNG thumbnail\n"); option_keys[0] = "compression"; option_values[0] = "9"; format = "png"; break; } } /* Try the first format given by the protocol without options */ if (format == NULL) { purple_debug_info("xfer", "creating thumbnail of format %s as demanded by protocol\n", formats_split[0]); format = formats_split[0]; } gdk_pixbuf_save_to_bufferv(thumbnail, &buffer, &size, format, option_keys, option_values, NULL); if (buffer) { gchar *mimetype = g_strdup_printf("image/%s", format); purple_debug_info("xfer", "created thumbnail of %" G_GSIZE_FORMAT " bytes\n", size); purple_xfer_set_thumbnail(xfer, buffer, size, mimetype); g_free(buffer); g_free(mimetype); } g_object_unref(thumbnail); g_strfreev(formats_split); } } } static void pidgin_xfer_progress_notify(PurpleXfer *xfer, G_GNUC_UNUSED GParamSpec *pspec, G_GNUC_UNUSED gpointer data) { pidgin_xfer_dialog_update_xfer(xfer_dialog, xfer); } static void pidgin_xfer_status_notify(PurpleXfer *xfer, G_GNUC_UNUSED GParamSpec *pspec, G_GNUC_UNUSED gpointer data) { if (xfer_dialog) { if (purple_xfer_is_cancelled(xfer)) { pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer); } } } static void pidgin_xfer_visible_notify(PurpleXfer *xfer, G_GNUC_UNUSED GParamSpec *pspec, G_GNUC_UNUSED gpointer data) { if (!purple_xfer_get_visible(xfer)) { return; } if (xfer_dialog == NULL) { xfer_dialog = pidgin_xfer_dialog_new(); } pidgin_xfer_dialog_add_xfer(xfer_dialog, xfer); } static void pidgin_xfer_new_xfer(PurpleXfer *xfer) { PidginXferUiData *data; /* This is where we're setting xfer's "ui_data" for the first time. */ data = g_new0(PidginXferUiData, 1); g_object_set_data_full(G_OBJECT(xfer), UI_DATA, data, g_free); g_signal_connect(xfer, "add-thumbnail", G_CALLBACK(pidgin_xfer_add_thumbnail), NULL); g_signal_connect(xfer, "notify::progress", G_CALLBACK(pidgin_xfer_progress_notify), NULL); g_signal_connect(xfer, "notify::status", G_CALLBACK(pidgin_xfer_status_notify), NULL); g_signal_connect(xfer, "notify::visible", G_CALLBACK(pidgin_xfer_visible_notify), NULL); } static PurpleXferUiOps ops = { .new_xfer = pidgin_xfer_new_xfer }; /************************************************************************** * GTK File Transfer API **************************************************************************/ void pidgin_xfers_init(void) { 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); } void pidgin_xfers_uninit(void) { g_clear_pointer(&xfer_dialog, pidgin_xfer_dialog_destroy); } void pidgin_set_xfer_dialog(PidginXferDialog *dialog) { xfer_dialog = dialog; } PidginXferDialog * pidgin_get_xfer_dialog(void) { return xfer_dialog; } PurpleXferUiOps * pidgin_xfers_get_ui_ops(void) { return &ops; }