summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcus Lundblad <malu@pidgin.im>2010-03-24 19:56:07 +0000
committerMarcus Lundblad <malu@pidgin.im>2010-03-24 19:56:07 +0000
commita1f2a1de170b4acf69a562d44acb3469760f9e3b (patch)
tree8a1c4c81e771614fa0d1398cfead1d100d2edaf0
parent2b122e0579149a10310f83e6d9a4b7bba2d548ef (diff)
parentd22aed61e7fbe87d476382ae616bf83ef67a1f2b (diff)
downloadpidgin-a1f2a1de170b4acf69a562d44acb3469760f9e3b.tar.gz
propagate from branch 'im.pidgin.pidgin' (head d4fffd2ce6646a061fef3aa38fba94a01b7499fc)
to branch 'im.pidgin.cpw.malu.ft_thumbnails' (head 200da593bfde0967c749edfc20af9b7264ea15f5)
-rw-r--r--libpurple/ft.c116
-rw-r--r--libpurple/ft.h54
-rw-r--r--libpurple/protocols/jabber/data.c26
-rw-r--r--libpurple/protocols/jabber/data.h7
-rw-r--r--libpurple/protocols/jabber/message.c2
-rw-r--r--libpurple/protocols/jabber/namespaces.h3
-rw-r--r--libpurple/protocols/jabber/si.c53
-rw-r--r--libpurple/protocols/msn/slp.c6
-rw-r--r--libpurple/protocols/msn/slplink.c40
-rw-r--r--libpurple/prpl.h9
-rw-r--r--libpurple/request.c58
-rw-r--r--libpurple/request.h44
-rw-r--r--pidgin/gtkft.c75
-rw-r--r--pidgin/gtkrequest.c40
14 files changed, 491 insertions, 42 deletions
diff --git a/libpurple/ft.c b/libpurple/ft.c
index a03737fa59..f26826032b 100644
--- a/libpurple/ft.c
+++ b/libpurple/ft.c
@@ -58,6 +58,10 @@ typedef struct _PurpleXferPrivData {
PURPLE_XFER_READY_PRPL = 0x2,
} ready;
GByteArray *buffer;
+
+ gpointer thumbnail_data; /**< thumbnail image */
+ gsize thumbnail_size;
+ gchar *thumbnail_mimetype;
} PurpleXferPrivData;
static int purple_xfer_choose_file(PurpleXfer *xfer);
@@ -70,6 +74,12 @@ purple_xfer_priv_data_destroy(gpointer data)
if (priv->buffer)
g_byte_array_free(priv->buffer, TRUE);
+ if (priv->thumbnail_data)
+ g_free(priv->thumbnail_data);
+
+ if (priv->thumbnail_mimetype)
+ g_free(priv->thumbnail_mimetype);
+
g_free(priv);
}
@@ -264,11 +274,14 @@ purple_xfer_set_status(PurpleXfer *xfer, PurpleXferStatusType status)
}
}
-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;
+ const gpointer *thumbnail_data = purple_xfer_get_thumbnail_data(xfer);
g_return_if_fail(xfer != NULL);
g_return_if_fail(message != NULL);
@@ -284,10 +297,40 @@ void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is
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;
+ gsize size = purple_xfer_get_thumbnail_size(xfer);
+ 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 puplically? */
+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;
@@ -471,13 +514,20 @@ purple_xfer_ask_recv(PurpleXfer *xfer)
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 (purple_xfer_get_thumbnail_data(xfer)) {
+ purple_request_accept_cancel_with_icon(xfer, NULL, buf, NULL,
+ PURPLE_DEFAULT_ACTION_NONE, xfer->account, xfer->who, NULL,
+ purple_xfer_get_thumbnail_data(xfer),
+ purple_xfer_get_thumbnail_size(xfer), 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);
@@ -545,10 +595,12 @@ purple_xfer_request(PurpleXfer *xfer)
{
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);
@@ -1575,6 +1627,50 @@ purple_xfer_update_progress(PurpleXfer *xfer)
ui_ops->update_progress(xfer, purple_xfer_get_progress(xfer));
}
+const void *
+purple_xfer_get_thumbnail_data(const PurpleXfer *xfer)
+{
+ PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
+
+ return priv->thumbnail_data;
+}
+
+gsize
+purple_xfer_get_thumbnail_size(const PurpleXfer *xfer)
+{
+ PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
+
+ return priv->thumbnail_size;
+}
+
+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);
+
+ if (thumbnail && size > 0) {
+ priv->thumbnail_data = g_memdup(thumbnail, size);
+ priv->thumbnail_size = size;
+ priv->thumbnail_mimetype = g_strdup(mimetype);
+ }
+}
+
+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
diff --git a/libpurple/ft.h b/libpurple/ft.h
index 9ca8d65582..83f6481496 100644
--- a/libpurple/ft.h
+++ b/libpurple/ft.h
@@ -120,7 +120,12 @@ typedef struct
*/
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,53 @@ void purple_xfer_ui_ready(PurpleXfer *xfer);
*/
void purple_xfer_prpl_ready(PurpleXfer *xfer);
+/**
+ * Gets the thumbnail data for a transfer
+ *
+ * @param xfer The file transfer to get the thumbnail for
+ * @return The thumbnail data, or NULL if there is no thumbnail
+ */
+const void *purple_xfer_get_thumbnail_data(const PurpleXfer *xfer);
+
+/**
+ * Gets the thumbnail size for a transfer
+ *
+ * @param xfer The file transfer to get the thumbnail size for
+ * @return The size, in bytes of the file transfer's thumbnail
+ */
+gsize purple_xfer_get_thumbnail_size(const PurpleXfer *xfer);
+
+/**
+ * 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
+ */
+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
+ */
+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.
+ */
+void purple_xfer_prepare_thumbnail(PurpleXfer *xfer, const gchar *formats);
+
+
/*@}*/
/**************************************************************************/
diff --git a/libpurple/protocols/jabber/data.c b/libpurple/protocols/jabber/data.c
index a9826c82bb..6e03fdab58 100644
--- a/libpurple/protocols/jabber/data.c
+++ b/libpurple/protocols/jabber/data.c
@@ -36,7 +36,7 @@ static GHashTable *remote_data_by_cid = NULL;
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 @@ jabber_data_create_from_data(gconstpointer rawdata, gsize size, const char *type
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 @@ jabber_data_create_from_xml(xmlnode *tag)
return data;
}
+void
+jabber_data_destroy(JabberData *data)
+{
+ jabber_data_delete(data);
+}
+
const char *
jabber_data_get_cid(const JabberData *data)
{
@@ -291,14 +298,14 @@ jabber_data_request(JabberStream *js, const gchar *cid, const gchar *who,
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);
}
@@ -308,6 +315,7 @@ jabber_data_find_remote_by_cid(JabberStream *js, const gchar *who,
{
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 =
@@ -325,9 +333,10 @@ jabber_data_find_remote_by_cid(JabberStream *js, const gchar *who,
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);
}
@@ -373,6 +382,11 @@ jabber_data_parse(JabberStream *js, const char *who, JabberIqType type,
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);
}
diff --git a/libpurple/protocols/jabber/data.h b/libpurple/protocols/jabber/data.h
index 6036b6aceb..4471ae20f3 100644
--- a/libpurple/protocols/jabber/data.h
+++ b/libpurple/protocols/jabber/data.h
@@ -34,6 +34,7 @@ typedef struct {
char *type;
gsize size;
gpointer data;
+ gboolean ephemeral;
} JabberData;
typedef void (JabberDataRequestCallback)(JabberData *data, gchar *alt,
@@ -42,12 +43,16 @@ typedef void (JabberDataRequestCallback)(JabberData *data, gchar *alt,
/* 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);
diff --git a/libpurple/protocols/jabber/message.c b/libpurple/protocols/jabber/message.c
index 7be9bc64a8..bfd651809f 100644
--- a/libpurple/protocols/jabber/message.c
+++ b/libpurple/protocols/jabber/message.c
@@ -963,7 +963,7 @@ jabber_message_smileyfy_xhtml(JabberMessage *jm, const char *xhtml)
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));
diff --git a/libpurple/protocols/jabber/namespaces.h b/libpurple/protocols/jabber/namespaces.h
index 243ef8e52d..3535f5f9dd 100644
--- a/libpurple/protocols/jabber/namespaces.h
+++ b/libpurple/protocols/jabber/namespaces.h
@@ -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"
diff --git a/libpurple/protocols/jabber/si.c b/libpurple/protocols/jabber/si.c
index 759ac0ba66..eebd07df65 100644
--- a/libpurple/protocols/jabber/si.c
+++ b/libpurple/protocols/jabber/si.c
@@ -32,6 +32,7 @@
#include "notify.h"
#include "buddy.h"
+#include "data.h"
#include "disco.h"
#include "jabber.h"
#include "ibb.h"
@@ -1248,7 +1249,8 @@ static void jabber_si_xfer_send_request(PurpleXfer *xfer)
char buf[32];
xfer->filename = g_path_get_basename(xfer->local_filename);
-
+ purple_xfer_prepare_thumbnail(xfer, "jpeg,png");
+
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 +1268,22 @@ static void jabber_si_xfer_send_request(PurpleXfer *xfer)
xmlnode_set_attrib(file, "size", buf);
/* maybe later we'll do hash and date attribs */
+ /* add thumbnail, if appropriate */
+ if (purple_xfer_get_thumbnail_data(xfer)) {
+ const gchar *mimetype = purple_xfer_get_thumbnail_mimetype(xfer);
+ JabberData *thumbnail_data =
+ jabber_data_create_from_data(purple_xfer_get_thumbnail_data(xfer),
+ purple_xfer_get_thumbnail_size(xfer), 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);
+ }
+
feature = xmlnode_new_child(si, "feature");
xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg");
x = xmlnode_new_child(feature, "x");
@@ -1644,12 +1662,27 @@ void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file
purple_xfer_request(xfer);
}
+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);
+}
+
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 +1764,22 @@ void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type,
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);
- purple_xfer_request(xfer);
+ /* 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);
+ }
}
void
diff --git a/libpurple/protocols/msn/slp.c b/libpurple/protocols/msn/slp.c
index b55e48b8e1..b7b4fcfa15 100644
--- a/libpurple/protocols/msn/slp.c
+++ b/libpurple/protocols/msn/slp.c
@@ -422,6 +422,12 @@ got_sessionreq(MsnSlpCall *slpcall, const char *branch,
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);
diff --git a/libpurple/protocols/msn/slplink.c b/libpurple/protocols/msn/slplink.c
index eeeed3090d..61d970cef8 100644
--- a/libpurple/protocols/msn/slplink.c
+++ b/libpurple/protocols/msn/slplink.c
@@ -662,15 +662,19 @@ static gchar *
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);
@@ -686,23 +690,37 @@ gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path)
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_data(xfer);
+ if (preview)
+ preview_len = purple_xfer_get_thumbnail_size(xfer);
+ else
+ preview_len = 0;
+ 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;
}
diff --git a/libpurple/prpl.h b/libpurple/prpl.h
index 4ed0def2fc..a0c509dbd2 100644
--- a/libpurple/prpl.h
+++ b/libpurple/prpl.h
@@ -52,6 +52,13 @@ typedef enum {
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.
*/
@@ -90,7 +97,7 @@ struct _PurpleBuddyIconSpec {
size_t max_filesize; /**< Maximum size in bytes */
PurpleIconScaleRules scale_rules; /**< How to stretch this icon */
};
-
+
/** Represents an entry containing information that must be supplied by the
* user when joining a chat.
*/
diff --git a/libpurple/request.c b/libpurple/request.c
index fe7198c799..2559110537 100644
--- a/libpurple/request.c
+++ b/libpurple/request.c
@@ -1317,6 +1317,29 @@ purple_request_action(void *handle, const char *title, const char *primary,
}
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 @@ purple_request_action_varg(void *handle, const char *title,
}
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 != 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,
diff --git a/libpurple/request.h b/libpurple/request.h
index 049b043b93..ea6fa932b1 100644
--- a/libpurple/request.h
+++ b/libpurple/request.h
@@ -237,10 +237,18 @@ typedef struct
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,27 @@ void *purple_request_action_varg(void *handle, const char *title,
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
+ */
+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.
+ */
+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 +1506,19 @@ void purple_request_close_with_handle(void *handle);
_("_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.
*
diff --git a/pidgin/gtkft.c b/pidgin/gtkft.c
index 78adbda91f..7da719d8bc 100644
--- a/pidgin/gtkft.c
+++ b/pidgin/gtkft.c
@@ -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,76 @@ pidgin_xfer_cancel_remote(PurpleXfer *xfer)
pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer);
}
+static void
+pidgin_xfer_add_thumbnail(PurpleXfer *xfer, const gchar *formats)
+{
+ purple_debug_info("pidgin", "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};
+ gboolean supports_jpeg = FALSE;
+ gboolean supports_png = FALSE;
+ int i;
+ gchar *format = NULL;
+
+ for (i = 0 ; formats_split[i] ; i++) {
+ if (purple_strequal(formats_split[i], "jpeg")) {
+ supports_jpeg = TRUE;
+ } else if (purple_strequal(formats_split[i], "png")) {
+ supports_png = TRUE;
+ }
+ }
+
+ /* prefer JPEG, then PNG, otherwise try the first format given
+ by the PRPL without options */
+ if (supports_jpeg) {
+ purple_debug_info("pidgin", "creating JPEG thumbnail\n");
+ option_keys[0] = "quality";
+ option_keys[1] = NULL;
+ option_values[0] = "90";
+ option_values[1] = NULL;
+ format = "jpeg";
+ } else if (supports_png) {
+ purple_debug_info("pidgin", "creating PNG thumbnail\n");
+ option_keys[0] = "compression";
+ option_keys[1] = NULL;
+ option_values[0] = "9";
+ option_values[1] = NULL;
+ format = "png";
+ } else {
+ purple_debug_info("pidgin",
+ "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) {
+ const gchar *mimetype = g_strdup_printf("image/%s", format);
+ purple_debug_info("pidgin",
+ "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 +1241,7 @@ static PurpleXferUiOps ops =
NULL,
NULL,
NULL,
- NULL
+ pidgin_xfer_add_thumbnail
};
/**************************************************************************
diff --git a/pidgin/gtkrequest.c b/pidgin/gtkrequest.c
index 3a164470f8..9848d7dc4b 100644
--- a/pidgin/gtkrequest.c
+++ b/pidgin/gtkrequest.c
@@ -26,6 +26,7 @@
#include "internal.h"
#include "pidgin.h"
+#include "debug.h"
#include "prefs.h"
#include "util.h"
@@ -592,9 +593,11 @@ pidgin_request_choice(const char *title, const char *primary,
}
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 @@ pidgin_request_action(const char *title, const char *primary,
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 @@ pidgin_request_action(const char *title, const char *primary,
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 @@ pidgin_request_action(const char *title, const char *primary,
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 @@ static PurpleRequestUiOps ops =
pidgin_request_file,
pidgin_close_request,
pidgin_request_folder,
- NULL,
+ pidgin_request_action_with_icon,
NULL,
NULL,
NULL