Mercurial > pidgin
changeset 29842:4eb6ac47f01f
propagate from branch 'im.pidgin.cpw.malu.ft_thumbnails' (head 98d8db26c6d12378d0113a97c8b3f989233a5cbc)
to branch 'im.pidgin.pidgin' (head 024731c3cb9e8c0e0479d91f68aeaf7fae9a978b)
author | Marcus Lundblad <ml@update.uu.se> |
---|---|
date | Wed, 28 Apr 2010 19:27:33 +0000 |
parents | 65c7e53d4bdb (current diff) f3a573191e43 (diff) |
children | e96c7113d32b 1f5c86f79eeb |
files | |
diffstat | 16 files changed, 501 insertions(+), 42 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Mon Apr 26 17:17:44 2010 +0000 +++ b/ChangeLog Wed Apr 28 19:27:33 2010 +0000 @@ -50,7 +50,9 @@ * The 'Message Timestamp Formats' plugin allows forcing 12-hour timestamps. (Jonathan Maltz) * Fix pastes from Chrome (rich-text pastes and probably URLs - having garbage appended to them) + having garbage appended to them). + * Show file transfer thumbnails for images on supporting protocols + (currently only supported on MSN). Bonjour: * Added support for IPv6. (Thanks to T_X for testing) @@ -76,6 +78,7 @@ MSN: * Support for version 9 of the MSN protocol has been removed. This version is no longer supported on the servers. + * Support file transfer thumbnails (previews) for images. XMPP: * Direct messages to a specific resource only upon receipt of a message
--- a/ChangeLog.API Mon Apr 26 17:17:44 2010 +0000 +++ b/ChangeLog.API Wed Apr 28 19:27:33 2010 +0000 @@ -33,6 +33,12 @@ * purple_socket_speaks_ipv4 * purple_unescape_text * purple_uuid_random + * purple_xfer_get_thumbnail + * purple_xfer_get_thumbnail_mimetype + * purple_xfer_set_thumbnail + * purple_xfer_prepare_thumbnail + * purple_request_action_with_icon + * purple_request_action_varg_with_icon * media_caps to the PurpleBuddy struct * buddy-caps-changed blist signal * ui-caps-changed media manager signal
--- a/libpurple/ft.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/ft.c Wed Apr 28 19:27:33 2010 +0000 @@ -60,6 +60,10 @@ /* TODO: Should really use a PurpleCircBuffer for this. */ GByteArray *buffer; + + gpointer thumbnail_data; /**< thumbnail image */ + gsize thumbnail_size; + gchar *thumbnail_mimetype; } PurpleXferPrivData; static int purple_xfer_choose_file(PurpleXfer *xfer); @@ -72,6 +76,10 @@ if (priv->buffer) g_byte_array_free(priv->buffer, TRUE); + g_free(priv->thumbnail_data); + + g_free(priv->thumbnail_mimetype); + g_free(priv); } @@ -266,15 +274,21 @@ } } -void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error) +static void +purple_xfer_conversation_write_internal(PurpleXfer *xfer, + const char *message, gboolean is_error, gboolean print_thumbnail) { PurpleConversation *conv = NULL; PurpleMessageFlags flags = PURPLE_MESSAGE_SYSTEM; char *escaped; + gconstpointer thumbnail_data; + gsize size; g_return_if_fail(xfer != NULL); g_return_if_fail(message != NULL); + thumbnail_data = purple_xfer_get_thumbnail(xfer, &size); + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, xfer->who, purple_xfer_get_account(xfer)); @@ -286,10 +300,39 @@ if (is_error) flags |= PURPLE_MESSAGE_ERROR; - purple_conversation_write(conv, NULL, escaped, flags, time(NULL)); + if (print_thumbnail && thumbnail_data) { + gchar *message_with_img; + gpointer data = g_memdup(thumbnail_data, size); + int id = purple_imgstore_add_with_id(data, size, NULL); + + message_with_img = + g_strdup_printf("<img id='%d'> %s", id, escaped); + purple_conversation_write(conv, NULL, message_with_img, flags, + time(NULL)); + purple_imgstore_unref_by_id(id); + g_free(message_with_img); + } else { + purple_conversation_write(conv, NULL, escaped, flags, time(NULL)); + } g_free(escaped); } +void +purple_xfer_conversation_write(PurpleXfer *xfer, gchar *message, + gboolean is_error) +{ + purple_xfer_conversation_write_internal(xfer, message, is_error, FALSE); +} + +/* maybe this one should be exported publically? */ +static void +purple_xfer_conversation_write_with_thumbnail(PurpleXfer *xfer, + const gchar *message) +{ + purple_xfer_conversation_write_internal(xfer, message, FALSE, TRUE); +} + + static void purple_xfer_show_file_error(PurpleXfer *xfer, const char *filename) { int err = errno; @@ -448,6 +491,8 @@ { char *buf, *size_buf; size_t size; + gconstpointer thumb; + gsize thumb_size; /* If we have already accepted the request, ask the destination file name directly */ @@ -473,13 +518,19 @@ serv_got_im(purple_account_get_connection(xfer->account), xfer->who, xfer->message, 0, time(NULL)); - purple_request_accept_cancel(xfer, NULL, buf, NULL, - PURPLE_DEFAULT_ACTION_NONE, - xfer->account, xfer->who, NULL, - xfer, - G_CALLBACK(purple_xfer_choose_file), - G_CALLBACK(cancel_recv_cb)); - + if ((thumb = purple_xfer_get_thumbnail(xfer, &thumb_size))) { + purple_request_accept_cancel_with_icon(xfer, NULL, buf, NULL, + PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL, + thumb, thumb_size, xfer, + G_CALLBACK(purple_xfer_choose_file), + G_CALLBACK(cancel_recv_cb)); + } else { + purple_request_accept_cancel(xfer, NULL, buf, NULL, + PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL, + xfer, G_CALLBACK(purple_xfer_choose_file), + G_CALLBACK(cancel_recv_cb)); + } + g_free(buf); } else purple_xfer_choose_file(xfer); @@ -547,10 +598,12 @@ { gchar* message = NULL; PurpleBuddy *buddy = purple_find_buddy(xfer->account, xfer->who); + message = g_strdup_printf(_("%s is offering to send file %s"), buddy ? purple_buddy_get_alias(buddy) : xfer->who, purple_xfer_get_filename(xfer)); - purple_xfer_conversation_write(xfer, message, FALSE); + purple_xfer_conversation_write_with_thumbnail(xfer, message); g_free(message); + /* Ask for a filename to save to if it's not already given by a plugin */ if (xfer->local_filename == NULL) purple_xfer_ask_recv(xfer); @@ -1580,6 +1633,52 @@ ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer)); } +gconstpointer +purple_xfer_get_thumbnail(const PurpleXfer *xfer, gsize *len) +{ + PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer); + + if (len) + *len = priv->thumbnail_size; + + return priv->thumbnail_data; +} + +const gchar * +purple_xfer_get_thumbnail_mimetype(const PurpleXfer *xfer) +{ + PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer); + + return priv->thumbnail_mimetype; +} + +void +purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail, + gsize size, const gchar *mimetype) +{ + PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer); + + g_free(priv->thumbnail_data); + g_free(priv->thumbnail_mimetype); + + if (thumbnail && size > 0) { + priv->thumbnail_data = g_memdup(thumbnail, size); + priv->thumbnail_size = size; + priv->thumbnail_mimetype = g_strdup(mimetype); + } else { + priv->thumbnail_data = NULL; + priv->thumbnail_size = 0; + priv->thumbnail_mimetype = NULL; + } +} + +void +purple_xfer_prepare_thumbnail(PurpleXfer *xfer, const gchar *formats) +{ + if (xfer->ui_ops->add_thumbnail) { + xfer->ui_ops->add_thumbnail(xfer, formats); + } +} /************************************************************************** * File Transfer Subsystem API
--- a/libpurple/ft.h Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/ft.h Wed Apr 28 19:27:33 2010 +0000 @@ -120,7 +120,12 @@ */ void (*data_not_sent)(PurpleXfer *xfer, const guchar *buffer, gsize size); - void (*_purple_reserved1)(void); + /** + * Op to create a thumbnail image for a file transfer + * + * @param xfer The file transfer structure + */ + void (*add_thumbnail)(PurpleXfer *xfer, const gchar *formats); } PurpleXferUiOps; /** @@ -687,6 +692,51 @@ */ void purple_xfer_prpl_ready(PurpleXfer *xfer); +/** + * Gets the thumbnail data for a transfer + * + * @param xfer The file transfer to get the thumbnail for + * @param len If not @c NULL, the length of the thumbnail data returned + * will be set in the location pointed to by this. + * @return The thumbnail data, or NULL if there is no thumbnail + * @since 2.7.0 + */ +gconstpointer purple_xfer_get_thumbnail(const PurpleXfer *xfer, gsize *len); + +/** + * Gets the mimetype of the thumbnail preview for a transfer + * + * @param xfer The file transfer to get the mimetype for + * @return The mimetype of the thumbnail, or @c NULL if not thumbnail is set + * @since 2.7.0 + */ +const gchar *purple_xfer_get_thumbnail_mimetype(const PurpleXfer *xfer); + + +/** + * Sets the thumbnail data for a transfer + * + * @param xfer The file transfer to set the data for + * @param thumbnail A pointer to the thumbnail data, this will be copied + * @param size The size in bytes of the passed in thumbnail data + * @param mimetype The mimetype of the generated thumbnail + * @since 2.7.0 + */ +void purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail, + gsize size, const gchar *mimetype); + +/** + * Prepare a thumbnail for a transfer (if the UI supports it) + * will be no-op in case the UI doesn't implement thumbnail creation + * + * @param xfer The file transfer to create a thumbnail for + * @param formats A comma-separated list of mimetypes for image formats + * the protocols can use for thumbnails. + * @since 2.7.0 + */ +void purple_xfer_prepare_thumbnail(PurpleXfer *xfer, const gchar *formats); + + /*@}*/ /**************************************************************************/
--- a/libpurple/protocols/jabber/data.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/jabber/data.c Wed Apr 28 19:27:33 2010 +0000 @@ -36,7 +36,7 @@ JabberData * jabber_data_create_from_data(gconstpointer rawdata, gsize size, const char *type, - JabberStream *js) + gboolean ephemeral, JabberStream *js) { JabberData *data = g_new0(JabberData, 1); gchar *checksum = jabber_calculate_data_hash(rawdata, size, "sha1"); @@ -48,6 +48,7 @@ data->cid = g_strdup(cid); data->type = g_strdup(type); data->size = size; + data->ephemeral = ephemeral; data->data = g_memdup(rawdata, size); @@ -110,6 +111,12 @@ return data; } +void +jabber_data_destroy(JabberData *data) +{ + jabber_data_delete(data); +} + const char * jabber_data_get_cid(const JabberData *data) { @@ -289,14 +296,14 @@ const JabberData * jabber_data_find_local_by_alt(const gchar *alt) { - purple_debug_info("jabber", "looking up local smiley with alt = %s\n", alt); + purple_debug_info("jabber", "looking up local data object with alt = %s\n", alt); return g_hash_table_lookup(local_data_by_alt, alt); } const JabberData * jabber_data_find_local_by_cid(const gchar *cid) { - purple_debug_info("jabber", "lookup local smiley with cid = %s\n", cid); + purple_debug_info("jabber", "lookup local data object with cid = %s\n", cid); return g_hash_table_lookup(local_data_by_cid, cid); } @@ -305,7 +312,7 @@ const gchar *cid) { const JabberData *data = g_hash_table_lookup(remote_data_by_cid, cid); - purple_debug_info("jabber", "lookup remote smiley with cid = %s\n", cid); + purple_debug_info("jabber", "lookup remote data object with cid = %s\n", cid); if (data == NULL) { gchar *jid_cid = @@ -323,9 +330,10 @@ void jabber_data_associate_local(JabberData *data, const gchar *alt) { - purple_debug_info("jabber", "associating local smiley\n alt = %s, cid = %s\n", - alt, jabber_data_get_cid(data)); - g_hash_table_insert(local_data_by_alt, g_strdup(alt), data); + purple_debug_info("jabber", "associating local data object\n alt = %s, cid = %s\n", + alt , jabber_data_get_cid(data)); + if (alt) + g_hash_table_insert(local_data_by_alt, g_strdup(alt), data); g_hash_table_insert(local_data_by_cid, g_strdup(jabber_data_get_cid(data)), data); } @@ -371,6 +379,11 @@ xmlnode_set_attrib(result->node, "id", id); xmlnode_insert_child(result->node, jabber_data_get_xml_definition(data)); + /* if the data object is temporary, destroy it and remove the references + to it */ + if (data->ephemeral) { + g_hash_table_remove(local_data_by_cid, cid); + } } jabber_iq_send(result); }
--- a/libpurple/protocols/jabber/data.h Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/jabber/data.h Wed Apr 28 19:27:33 2010 +0000 @@ -34,6 +34,7 @@ char *type; gsize size; gpointer data; + gboolean ephemeral; } JabberData; typedef void (JabberDataRequestCallback)(JabberData *data, gchar *alt, @@ -42,12 +43,16 @@ /* creates a JabberData instance from raw data */ JabberData *jabber_data_create_from_data(gconstpointer data, gsize size, - const char *type, JabberStream *js); + const char *type, gboolean ephemeral, JabberStream *js); /* create a JabberData instance from an XML "data" element (as defined by XEP 0231 */ JabberData *jabber_data_create_from_xml(xmlnode *tag); +/* destroy a JabberData instance, NOT to be used on data that has been + associated, since they get "owned" */ +void jabber_data_destroy(JabberData *data); + const char *jabber_data_get_cid(const JabberData *data); const char *jabber_data_get_type(const JabberData *data);
--- a/libpurple/protocols/jabber/message.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/jabber/message.c Wed Apr 28 19:27:33 2010 +0000 @@ -963,7 +963,7 @@ JabberData *new_data = jabber_data_create_from_data(purple_imgstore_get_data(image), purple_imgstore_get_size(image), - jabber_message_get_mimetype_from_ext(ext), js); + jabber_message_get_mimetype_from_ext(ext), FALSE, js); purple_debug_info("jabber", "cache local smiley alt = %s, cid = %s\n", shortcut, jabber_data_get_cid(new_data));
--- a/libpurple/protocols/jabber/namespaces.h Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/jabber/namespaces.h Wed Apr 28 19:27:33 2010 +0000 @@ -89,6 +89,9 @@ /* XEP-0237 Roster Versioning */ #define NS_ROSTER_VERSIONING "urn:xmpp:features:rosterver" +/* XEP-0264 File Transfer Thumbnails (Thumbs) */ +#define NS_THUMBS "urn:xmpp:thumbs:0" + /* Google extensions */ #define NS_GOOGLE_CAMERA "http://www.google.com/xmpp/protocol/camera/v1" #define NS_GOOGLE_VIDEO "http://www.google.com/xmpp/protocol/video/v1"
--- a/libpurple/protocols/jabber/si.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/jabber/si.c Wed Apr 28 19:27:33 2010 +0000 @@ -32,6 +32,7 @@ #include "notify.h" #include "buddy.h" +#include "data.h" #include "disco.h" #include "jabber.h" #include "ibb.h" @@ -39,6 +40,7 @@ #include "si.h" #define STREAMHOST_CONNECT_TIMEOUT 15 +#define ENABLE_FT_THUMBNAILS 0 typedef struct _JabberSIXfer { JabberStream *js; @@ -1246,9 +1248,14 @@ JabberIq *iq; xmlnode *si, *file, *feature, *x, *field, *option, *value; char buf[32]; +#if ENABLE_FT_THUMBNAILS + gconstpointer thumb; + gsize thumb_size; + purple_xfer_prepare_thumbnail(xfer, "jpeg,png"); +#endif xfer->filename = g_path_get_basename(xfer->local_filename); - + iq = jabber_iq_new(jsx->js, JABBER_IQ_SET); xmlnode_set_attrib(iq->node, "to", xfer->who); si = xmlnode_new_child(iq->node, "si"); @@ -1266,6 +1273,23 @@ xmlnode_set_attrib(file, "size", buf); /* maybe later we'll do hash and date attribs */ +#if ENABLE_FT_THUMBNAILS + /* add thumbnail, if appropriate */ + if ((thumb = purple_xfer_get_thumbnail(xfer, &thumb_size))) { + const gchar *mimetype = purple_xfer_get_thumbnail_mimetype(xfer); + JabberData *thumbnail_data = + jabber_data_create_from_data(thumb, thumb_size, + mimetype, TRUE, jsx->js); + xmlnode *thumbnail = xmlnode_new_child(file, "thumbnail"); + xmlnode_set_namespace(thumbnail, NS_THUMBS); + xmlnode_set_attrib(thumbnail, "cid", + jabber_data_get_cid(thumbnail_data)); + xmlnode_set_attrib(thumbnail, "mime-type", mimetype); + /* cache data */ + jabber_data_associate_local(thumbnail_data, NULL); + } +#endif + feature = xmlnode_new_child(si, "feature"); xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); x = xmlnode_new_child(feature, "x"); @@ -1644,12 +1668,29 @@ purple_xfer_request(xfer); } +#if ENABLE_FT_THUMBNAILS +static void +jabber_si_thumbnail_cb(JabberData *data, gchar *alt, gpointer userdata) +{ + PurpleXfer *xfer = (PurpleXfer *) userdata; + + if (data) { + purple_xfer_set_thumbnail(xfer, jabber_data_get_data(data), + jabber_data_get_size(data), jabber_data_get_type(data)); + /* data is ephemeral, get rid of now (the xfer re-owned the thumbnail */ + jabber_data_destroy(data); + } + + purple_xfer_request(xfer); +} +#endif + void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *si) { JabberSIXfer *jsx; PurpleXfer *xfer; - xmlnode *file, *feature, *x, *field, *option, *value; + xmlnode *file, *feature, *x, *field, *option, *value, *thumbnail; const char *stream_id, *filename, *filesize_c, *profile; size_t filesize = 0; @@ -1731,10 +1772,26 @@ purple_xfer_set_request_denied_fnc(xfer, jabber_si_xfer_request_denied); purple_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv); purple_xfer_set_end_fnc(xfer, jabber_si_xfer_end); - + js->file_transfers = g_list_append(js->file_transfers, xfer); +#if ENABLE_FT_THUMBNAILS + /* if there is a thumbnail, we should request it... */ + if ((thumbnail = xmlnode_get_child_with_namespace(file, "thumbnail", + NS_THUMBS))) { + const char *cid = xmlnode_get_attrib(thumbnail, "cid"); + if (cid) { + jabber_data_request(js, cid, purple_xfer_get_remote_user(xfer), + NULL, TRUE, jabber_si_thumbnail_cb, xfer); + } else { + purple_xfer_request(xfer); + } + } else { + purple_xfer_request(xfer); + } +#else purple_xfer_request(xfer); +#endif } void
--- a/libpurple/protocols/msn/slp.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/msn/slp.c Wed Apr 28 19:27:33 2010 +0000 @@ -422,6 +422,12 @@ xfer->data = slpcall; + if (header->type == 0 && bin_len >= sizeof(MsnFileContext)) { + purple_xfer_set_thumbnail(xfer, &header->preview, + bin_len - sizeof(MsnFileContext), + "image/png"); + } + purple_xfer_request(xfer); } g_free(header);
--- a/libpurple/protocols/msn/slplink.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/protocols/msn/slplink.c Wed Apr 28 19:27:33 2010 +0000 @@ -692,15 +692,19 @@ gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path) { gsize size = 0; - MsnFileContext header; + MsnFileContext *header; gchar *u8 = NULL; gchar *ret; gunichar2 *uni = NULL; glong currentChar = 0; glong len = 0; + const char *preview; + gsize preview_len; size = purple_xfer_get_size(xfer); + purple_xfer_prepare_thumbnail(xfer, "png"); + if (!file_name) { gchar *basename = g_path_get_basename(file_path); u8 = purple_utf8_try_convert(basename); @@ -716,23 +720,33 @@ u8 = NULL; } - header.length = GUINT32_TO_LE(sizeof(MsnFileContext) - 1); - header.version = GUINT32_TO_LE(2); /* V.3 contains additional unnecessary data */ - header.file_size = GUINT64_TO_LE(size); - header.type = GUINT32_TO_LE(1); /* No file preview */ + preview = purple_xfer_get_thumbnail(xfer, &preview_len); + header = g_malloc(sizeof(MsnFileContext) + preview_len); + + header->length = GUINT32_TO_LE(sizeof(MsnFileContext) - 1); + header->version = GUINT32_TO_LE(2); /* V.3 contains additional unnecessary data */ + header->file_size = GUINT64_TO_LE(size); + if (preview) + header->type = GUINT32_TO_LE(0); + else + header->type = GUINT32_TO_LE(1); len = MIN(len, MAX_FILE_NAME_LEN); for (currentChar = 0; currentChar < len; currentChar++) { - header.file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]); + header->file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]); } - memset(&header.file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2); + memset(&header->file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2); - memset(&header.unknown1, 0, sizeof(header.unknown1)); - header.unknown2 = GUINT32_TO_LE(0xffffffff); - header.preview[0] = '\0'; + memset(&header->unknown1, 0, sizeof(header->unknown1)); + header->unknown2 = GUINT32_TO_LE(0xffffffff); + if (preview) { + memcpy(&header->preview, preview, preview_len); + } + header->preview[preview_len] = '\0'; g_free(uni); - ret = purple_base64_encode((const guchar *)&header, sizeof(MsnFileContext)); + ret = purple_base64_encode((const guchar *)header, sizeof(MsnFileContext) + preview_len); + g_free(header); return ret; }
--- a/libpurple/prpl.h Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/prpl.h Wed Apr 28 19:27:33 2010 +0000 @@ -52,6 +52,13 @@ typedef struct _PurpleBuddyIconSpec PurpleBuddyIconSpec; /** + * A description of a file transfer thumbnail specification. + * This tells the UI if and what image formats the prpl support for file + * transfer thumbnails. + */ +typedef struct _PurpleThumbnailSpec PurpleThumbnailSpec; + +/** * This \#define exists just to make it easier to fill out the buddy icon * field in the prpl info struct for protocols that couldn't care less. */
--- a/libpurple/request.c Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/request.c Wed Apr 28 19:27:33 2010 +0000 @@ -1317,6 +1317,29 @@ } void * +purple_request_action_with_icon(void *handle, const char *title, + const char *primary, + const char *secondary, int default_action, + PurpleAccount *account, const char *who, + PurpleConversation *conv, gconstpointer icon_data, + gsize icon_size, void *user_data, size_t action_count, ...) +{ + void *ui_handle; + va_list args; + + g_return_val_if_fail(action_count > 0, NULL); + + va_start(args, action_count); + ui_handle = purple_request_action_varg_with_icon(handle, title, primary, + secondary, default_action, account, who, conv, icon_data, icon_size, + user_data, action_count, args); + va_end(args); + + return ui_handle; +} + + +void * purple_request_action_varg(void *handle, const char *title, const char *primary, const char *secondary, int default_action, @@ -1348,6 +1371,41 @@ } void * +purple_request_action_varg_with_icon(void *handle, const char *title, + const char *primary, const char *secondary, + int default_action, + PurpleAccount *account, const char *who, + PurpleConversation *conv, gconstpointer icon_data, + gsize icon_size, + void *user_data, size_t action_count, va_list actions) +{ + PurpleRequestUiOps *ops; + + g_return_val_if_fail(action_count > 0, NULL); + + ops = purple_request_get_ui_ops(); + + if (ops != NULL && ops->request_action_with_icon != NULL) { + PurpleRequestInfo *info; + + info = g_new0(PurpleRequestInfo, 1); + info->type = PURPLE_REQUEST_ACTION; + info->handle = handle; + info->ui_handle = ops->request_action_with_icon(title, primary, secondary, + default_action, account, who, conv, + icon_data, icon_size, + user_data, action_count, actions); + + handles = g_list_append(handles, info); + + return info->ui_handle; + } + + return NULL; +} + + +void * purple_request_fields(void *handle, const char *title, const char *primary, const char *secondary, PurpleRequestFields *fields, const char *ok_text, GCallback ok_cb,
--- a/libpurple/request.h Mon Apr 26 17:17:44 2010 +0000 +++ b/libpurple/request.h Wed Apr 28 19:27:33 2010 +0000 @@ -237,10 +237,18 @@ PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data); + /** @see purple_request_action_varg_with_icon(). */ + void *(*request_action_with_icon)(const char *title, const char *primary, + const char *secondary, int default_action, + PurpleAccount *account, const char *who, + PurpleConversation *conv, + gconstpointer icon_data, gsize icon_size, + void *user_data, + size_t action_count, va_list actions); + void (*_purple_reserved1)(void); void (*_purple_reserved2)(void); void (*_purple_reserved3)(void); - void (*_purple_reserved4)(void); } PurpleRequestUiOps; typedef void (*PurpleRequestInputCb)(void *, const char *); @@ -1393,6 +1401,29 @@ void *user_data, size_t action_count, va_list actions); /** + * Version of purple_request_action() supplying an image for the UI to + * optionally display as an icon in the dialog; see its documentation + * @since 2.7.0 + */ +void *purple_request_action_with_icon(void *handle, const char *title, + const char *primary, const char *secondary, int default_action, + PurpleAccount *account, const char *who, PurpleConversation *conv, + gconstpointer icon_data, gsize icon_size, void *user_data, + size_t action_count, ...); + +/** + * <tt>va_list</tt> version of purple_request_action_with_icon(); + * see its documentation. + * @since 2.7.0 + */ +void *purple_request_action_varg_with_icon(void *handle, const char *title, + const char *primary, const char *secondary, int default_action, + PurpleAccount *account, const char *who, PurpleConversation *conv, + gconstpointer icon_data, gsize icon_size, + void *user_data, size_t action_count, va_list actions); + + +/** * Displays groups of fields for the user to fill in. * * @param handle The plugin or connection handle. For some things this @@ -1477,6 +1508,19 @@ _("_Accept"), (accept_cb), _("_Cancel"), (cancel_cb)) /** + * A wrapper for purple_request_action_with_icon() that uses Accept and Cancel + * buttons. + */ +#define purple_request_accept_cancel_with_icon(handle, title, primary, secondary, \ + default_action, account, who, conv, \ + icon_data, icon_size, \ + user_data, accept_cb, cancel_cb) \ + purple_request_action_with_icon((handle), (title), (primary), (secondary), \ + (default_action), account, who, conv, icon_data, icon_size, \ + (user_data), 2, \ + _("_Accept"), (accept_cb), _("_Cancel"), (cancel_cb)) + +/** * Displays a file selector request dialog. Returns the selected filename to * the callback. Can be used for either opening a file or saving a file. *
--- a/pidgin/gtkft.c Mon Apr 26 17:17:44 2010 +0000 +++ b/pidgin/gtkft.c Wed Apr 28 19:27:33 2010 +0000 @@ -40,6 +40,9 @@ #define PIDGINXFER(xfer) \ (PidginXferUiData *)(xfer)->ui_data +/* 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 { gboolean keep_open; @@ -1157,6 +1160,67 @@ pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer); } +static void +pidgin_xfer_add_thumbnail(PurpleXfer *xfer, const gchar *formats) +{ + purple_debug_info("ft", "creating thumbnail for transfer\n"); + + if (purple_xfer_get_size(xfer) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL) { + GdkPixbuf *thumbnail = + gdk_pixbuf_new_from_file_at_size( + purple_xfer_get_local_filename(xfer), 128, 128, NULL); + + 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("ft", "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("ft", "creating PNG thumbnail\n"); + option_keys[0] = "compression"; + option_values[0] = "9"; + format = "png"; + break; + } + } + + /* Try the first format given by the PRPL without options */ + if (format == NULL) { + purple_debug_info("ft", + "creating thumbnail of format %s as demanded by PRPL\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("ft", + "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 PurpleXferUiOps ops = { pidgin_xfer_new_xfer, @@ -1168,7 +1232,7 @@ NULL, NULL, NULL, - NULL + pidgin_xfer_add_thumbnail }; /**************************************************************************
--- a/pidgin/gtkrequest.c Mon Apr 26 17:17:44 2010 +0000 +++ b/pidgin/gtkrequest.c Wed Apr 28 19:27:33 2010 +0000 @@ -26,6 +26,7 @@ #include "internal.h" #include "pidgin.h" +#include "debug.h" #include "prefs.h" #include "util.h" @@ -592,9 +593,11 @@ } static void * -pidgin_request_action(const char *title, const char *primary, +pidgin_request_action_with_icon(const char *title, const char *primary, const char *secondary, int default_action, - PurpleAccount *account, const char *who, PurpleConversation *conv, + PurpleAccount *account, const char *who, + PurpleConversation *conv, gconstpointer icon_data, + gsize icon_size, void *user_data, size_t action_count, va_list actions) { PidginRequestData *data; @@ -602,7 +605,7 @@ GtkWidget *vbox; GtkWidget *hbox; GtkWidget *label; - GtkWidget *img; + GtkWidget *img = NULL; void **buttons; char *label_text; char *primary_esc, *secondary_esc; @@ -659,8 +662,25 @@ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox); /* Dialog icon. */ - img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION, + if (icon_data) { + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + GdkPixbuf *pixbuf = NULL; + if (gdk_pixbuf_loader_write(loader, icon_data, icon_size, NULL)) { + pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); + if (pixbuf) { + img = gtk_image_new_from_pixbuf(pixbuf); + } + } else { + purple_debug_info("pidgin", "failed to parse dialog icon\n"); + } + gdk_pixbuf_loader_close(loader, NULL); + g_object_unref(loader); + } + + if (!img) { + img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE)); + } gtk_misc_set_alignment(GTK_MISC(img), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); @@ -712,6 +732,16 @@ return data; } +static void * +pidgin_request_action(const char *title, const char *primary, + const char *secondary, int default_action, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data, size_t action_count, va_list actions) +{ + pidgin_request_action_with_icon(title, primary, secondary, default_action, + account, who, conv, NULL, 0, user_data, action_count, actions); +} + static void req_entry_field_changed_cb(GtkWidget *entry, PurpleRequestField *field) { @@ -1699,7 +1729,7 @@ pidgin_request_file, pidgin_close_request, pidgin_request_folder, - NULL, + pidgin_request_action_with_icon, NULL, NULL, NULL