All pastes #921059 Raw Edit

Miscellany

public c v1 · immutable
#921059 ·published 2008-02-27 20:03 UTC
rendered paste body
/* * Adium Webkit views * Copyright (C) 2007 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. *//* This plugins is basically Sadrul's x-chat plugin, but with Webkit * instead of xtext. */#define PLUGIN_ID		"gtk-adium-ims"#define PLUGIN_NAME		"adium-ims"#define PLUGIN_AUTHOR		"Sean Egan <seanegan@gmail.com>"#define PURPLE_PLUGINS          "Hell yeah"/* System headers */#include <string.h>#include <gdk/gdk.h>#include <gtk/gtk.h>#include <webkitgtkpage.h>/* Purple headers */#include <conversation.h>#include <util.h>#include <version.h>/* Pidgin headers */#include <gtkconv.h>#include <gtkplugin.h>static PurpleConversationUiOps *uiops = NULL;static void (*default_write_conv)(PurpleConversation *conv, const char *name, const char *alias,						   const char *message, PurpleMessageFlags flags, time_t mtime);static void (*default_create_conversation)(PurpleConversation *conv);static void (*default_destroy_conversation)(PurpleConversation *conv);static GtkWidget* hack_and_get_widget(PidginConversation *gtkconv);typedef struct _PidginWebkit PidginWebkit;struct _PidginWebkit{	GtkWidget *imhtml;	GtkWidget *webkit;};static GHashTable *webkits = NULL;		/* Hashtable of webkits *//* Cache the contents of the HTML files */char *template_html = NULL;                 /* This is the skeleton: some basic javascript mostly */char *header_html = NULL;                   /* This is the first thing to be appended to any conversation */char *incoming_content_html = NULL;         /* This is a received message */char *outgoing_content_html = NULL;         /* And a sent one */char *incoming_next_content_html = NULL;    /* The same things, but used when someone sends multiple subsequent */char *outgoing_next_content_html = NULL;    /* messages in a row */char *status_html = NULL;                   /* Non-IM status messages *//* Cache their lenghts too, to pass into g_string_new_len, avoiding crazy allocation */gsize template_html_len = 0;gsize header_html_len = 0;gsize incoming_content_html_len = 0;gsize outgoing_content_html_len = 0;gsize incoming_next_content_html_len = 0;gsize outgoing_next_content_html_len = 0;gsize status_html_len = 0;/* And their paths */char *template_path = NULL;char *css_path = NULL;char *replace_message_tokens(char *text, gsize len, PurpleConversation *conv, const char *name, const char *alias, 			     const char *message, PurpleMessageFlags flags, time_t mtime){	GString *str = g_string_new_len(NULL, len);	char *cur = text;	char *prev = cur;	while ((cur = strchr(cur, '%'))) {		const char *replace = NULL;					if (!strncmp(cur, "%message%", strlen("%message%"))) {			replace = message;		} else if (!strncmp(cur, "%messageClasses%", strlen("%messageClasses%"))) {			replace = flags & PURPLE_MESSAGE_SEND ? "outgoing" :				  flags & PURPLE_MESSAGE_RECV ? "incoming" : "event";		} else if (!strncmp(cur, "%time%", strlen("%time%"))) {			replace = purple_utf8_strftime("%X", NULL);		} else if (!strncmp(cur, "%userIconPath%", strlen("%userIconPath%"))) {			if (flags & PURPLE_MESSAGE_SEND) {				if (purple_account_get_bool(conv->account, "use-global-buddyicon", TRUE)) {					replace = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");				} else {					PurpleStoredImage *img = purple_buddy_icons_find_account_icon(conv->account);					replace = purple_imgstore_get_filename(img);				}			} else if (flags & PURPLE_MESSAGE_RECV) {				PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));				replace = purple_buddy_icon_get_full_path(icon);			}		} else if (!strncmp(cur, "%senderScreenName%", strlen("%senderScreenName%"))) {			replace = name;		} else if (!strncmp(cur, "%sender%", strlen("%sender%"))) {			replace = alias;		} else {			cur++;			continue;		}		/* Here we have a replacement to make */		g_string_append_len(str, prev, cur - prev);		g_string_append(str, replace);		/* And update the pointers */		prev = cur = strchr(cur + 1, '%') + 1;	}		/* And wrap it up */	g_string_append(str, prev);	return g_string_free(str, FALSE);}char *replace_header_tokens(char *text, gsize len, PurpleConversation *conv){	GString *str = g_string_new_len(NULL, len);	char *cur = text;	char *prev = cur;	while ((cur = strchr(cur, '%'))) {		const char *replace = NULL;  		if (!strncmp(cur, "%chatName%", strlen("%chatName%"))) {			replace = conv->name;  		} else if (!strncmp(cur, "%sourceName%", strlen("%sourceName%"))) {			replace = purple_account_get_alias(conv->account);			if (replace == NULL)				replace = purple_account_get_username(conv->account);  		} else if (!strncmp(cur, "%destinationName%", strlen("%destinationName%"))) {			PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);			if (buddy) {				replace = purple_buddy_get_alias(buddy);			} else {				replace = conv->name;			}  		} else if (!strncmp(cur, "%incomingIconPath%", strlen("%incomingIconPath%"))) {			PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));			replace = purple_buddy_icon_get_full_path(icon);  		} else if (!strncmp(cur, "%outgoingIconPath%", strlen("%outgoingIconPath%"))) {  		} else if (!strncmp(cur, "%timeOpened", strlen("%timeOpened"))) {			char *format = NULL;			if (*(cur + strlen("%timeOpened")) == '{') {				char *start = cur + strlen("%timeOpened");				char *end = strstr(start, "}%");				if (!end) /* Invalid string */					continue;				format = g_strndup(start, end - start);			} 			replace = purple_utf8_strftime(format ? format : "%X", NULL);			g_free(format);		} else {			cur++;			continue;		}		/* Here we have a replacement to make */		g_string_append_len(str, prev, cur - prev);		g_string_append(str, replace);		/* And update the pointers */		prev = cur = strchr(cur + 1, '%') + 1;	}		/* And wrap it up */	g_string_append(str, prev);	return g_string_free(str, FALSE);}char *replace_template_tokens(char *text, int len, char *header) {	GString *str = g_string_new_len(NULL, len);	char **ms = g_strsplit(text, "%@", 5);		if (ms[0] == NULL || ms[1] == NULL || ms[2] == NULL || ms[3] == NULL || ms[4] == NULL) {		g_strfreev(ms);		g_string_free(str, TRUE);		return NULL;	}	g_string_append(str, ms[0]);	g_string_append(str, template_path);	g_string_append(str, ms[1]);	g_string_append(str, css_path);	g_string_append(str, ms[2]);	g_string_append(str, header);	g_string_append(str, ms[3]);	g_string_append(str, ms[4]);		g_strfreev(ms);	return g_string_free(str, FALSE);}GtkWidget *get_webkit(PurpleConversation *conv){	PidginWebkit *gx;	if ((gx = g_hash_table_lookup(webkits, conv)) == NULL)	{		PidginConversation *gtkconv;		GtkWidget *webkit;		GtkWidget *imhtml = NULL;		char *header;		char *template;		gtkconv = PIDGIN_CONVERSATION(conv);		if (!gtkconv)			return NULL;		imhtml = gtkconv->imhtml;		gx = g_new0(PidginWebkit, 1);		webkit = webkit_page_new();		header = replace_header_tokens(header_html, header_html_len, conv);		template = replace_template_tokens(template_html, template_html_len + header_html_len, header);		webkit_page_load_string(WEBKIT_PAGE(webkit), template, "text/html", "UTF-8", template_path);				gx->webkit = webkit;		gx->imhtml = hack_and_get_widget(gtkconv);		g_hash_table_insert(webkits, conv, gx);		g_free(header);		g_free(template);	}	return gx->webkit;}char *escape_message(char *text){	GString *str = g_string_new(NULL);	char *cur = text;	while (cur && *cur) {		switch (*cur) {		case '\\':			g_string_append(str, "\\\\");				break;		case '\"':			g_string_append(str, "\\\"");			break;		case '\r':			g_string_append(str, "<br/>");			break;		case '\n':			break;		default:			g_string_append_c(str, *cur);		}		cur++;	}	return g_string_free(str, FALSE);}static void purple_webkit_write_conv(PurpleConversation *conv, const char *name, const char *alias,						   const char *message, PurpleMessageFlags flags, time_t mtime){	PurpleConversationType type;	GtkWidget *webkit;	char *stripped;	char *message_html;	char *msg;	char *escape;	char *script;	char *func = "appendMessage";	PurpleMessageFlags old_flags = GPOINTER_TO_INT(purple_conversation_get_data(conv, "webkit-lastflags")); 	/* Do the usual stuff first. */	default_write_conv(conv, name, alias, message, flags, mtime);	type = purple_conversation_get_type(conv);	if (type != PURPLE_CONV_TYPE_IM)	{		/* If it's chat, we have nothing to do. */		return;	}	/* So it's an IM. Let's play. */	webkit = get_webkit(conv);	stripped = purple_markup_strip_html(message);	if (flags & PURPLE_MESSAGE_SEND && old_flags & PURPLE_MESSAGE_SEND) {		message_html = outgoing_next_content_html;		func = "appendNextMessage";	} else if (flags & PURPLE_MESSAGE_SEND) {		message_html = outgoing_content_html;	} else if (flags & PURPLE_MESSAGE_RECV && old_flags & PURPLE_MESSAGE_RECV) {		message_html = incoming_next_content_html;		func = "appendNextMessage";	} else if (flags & PURPLE_MESSAGE_RECV) {		message_html = incoming_content_html;	} else {		message_html = status_html;	}	purple_conversation_set_data(conv, "webkit-lastflags", GINT_TO_POINTER(flags));	msg = replace_message_tokens(message_html, 0, conv, name, alias, stripped, flags, mtime);	escape = escape_message(msg);	script = g_strdup_printf("%s(\"%s\")", func, escape);	webkit_page_execute_script(WEBKIT_PAGE(webkit), script);	g_free(msg);	g_free(stripped);	g_free(escape);	g_free(script);}static GtkWidget*hack_and_get_widget(PidginConversation *gtkconv){	GtkWidget *tab_cont, *pane, *vbox, *hpaned;	GList *list;	const char *name;	/* If you think this is ugly, you are right. */#define	DEBUG_INFO(x)	\	name = G_OBJECT_TYPE_NAME(x);	\	printf("%s\n", name)		tab_cont = gtkconv->tab_cont;	DEBUG_INFO(tab_cont);	list = gtk_container_get_children(GTK_CONTAINER(tab_cont));	vbox = list->data;	g_list_free(list);	DEBUG_INFO(vbox);	list = GTK_BOX(vbox)->children;	hpaned = ((GtkBoxChild*)list->next->data)->widget;	vbox = GTK_BIN(hpaned)->child;		DEBUG_INFO(vbox);	list = GTK_BOX(vbox)->children;	pane = ((GtkBoxChild*)list->data)->widget;	DEBUG_INFO(pane);	return pane;}static voidpurple_conversation_use_webkit(PurpleConversation *conv){	PidginConversation *gtkconv;	GtkWidget *parent, *box, *webkit, *frame;	if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM)		return;	gtkconv = PIDGIN_CONVERSATION(conv);	if (!gtkconv)		return;	frame = hack_and_get_widget(gtkconv);	parent = frame->parent;	gtk_widget_hide_all(frame);	g_object_ref(frame);	box = gtk_hbox_new(FALSE, 0);	webkit = get_webkit(conv);	gtk_container_add(GTK_CONTAINER(parent), box);	gtk_box_pack_start(GTK_BOX(box), webkit, TRUE, TRUE, 0);	gtk_widget_show_all(box);	gtk_widget_realize(webkit);}static voidpurple_webkit_create_conv(PurpleConversation *conv){	default_create_conversation(conv);	purple_conversation_use_webkit(conv);}static voidpurple_webkit_destroy_conv(PurpleConversation *conv){	PidginWebkit *gx;	default_destroy_conversation(conv);	gx = g_hash_table_lookup(webkits, conv);	if (gx)	{		g_free(gx);		g_hash_table_remove(webkits, conv);	}}static gbooleanplugin_load(PurplePlugin *plugin){	GList *list;	char *style_dir = g_build_filename(purple_user_dir(), "style", NULL);	char *file;		css_path = g_build_filename(style_dir, "Contents", "Resources", "main.css", NULL);	template_path = g_build_filename(style_dir, "Contents", "Resources", "Template.html", NULL);	if (!g_file_get_contents(template_path, &template_html, &template_html_len, NULL))		return FALSE;		file = g_build_filename(style_dir, "Contents", "Resources", "Header.html", NULL);	if (!g_file_get_contents(file, &header_html, &header_html_len, NULL))		return FALSE;	file = g_build_filename(style_dir, "Contents", "Resources", "Outgoing", "Content.html", NULL);	if (!g_file_get_contents(file, &outgoing_content_html, &outgoing_content_html_len, NULL))		return FALSE;	file = g_build_filename(style_dir, "Contents", "Resources", "Outgoing", "NextContent.html", NULL);	if (!g_file_get_contents(file, &outgoing_next_content_html, &outgoing_next_content_html_len, NULL))		return FALSE;	file = g_build_filename(style_dir, "Contents", "Resources", "Incoming", "Content.html", NULL);	if (!g_file_get_contents(file, &incoming_content_html, &incoming_content_html_len, NULL))		return FALSE;	file = g_build_filename(style_dir, "Contents", "Resources", "Incoming", "NextContent.html", NULL);	if (!g_file_get_contents(file, &incoming_next_content_html, &incoming_next_content_html_len, NULL))		return FALSE;	file = g_build_filename(style_dir, "Contents", "Resources", "Status.html", NULL);	if (!g_file_get_contents(file, &status_html, &status_html_len, NULL))		return FALSE;	uiops = pidgin_conversations_get_conv_ui_ops();	if (uiops == NULL)		return FALSE;	/* Use the oh-so-useful uiops. Signals? bleh. */	default_write_conv = uiops->write_conv;	uiops->write_conv = purple_webkit_write_conv;	default_create_conversation = uiops->create_conversation;	uiops->create_conversation = purple_webkit_create_conv;	default_destroy_conversation = uiops->destroy_conversation;	uiops->destroy_conversation = purple_webkit_destroy_conv;	webkits = g_hash_table_new(g_direct_hash, g_direct_equal);	list = purple_get_chats();	while (list)	{		purple_conversation_use_webkit(list->data);		list = list->next;	}		return TRUE;}static void remove_webkit(PurpleConversation *conv, PidginWebkit *gx, gpointer null){	GtkWidget *frame, *parent;	frame = gx->webkit->parent;	parent = frame->parent;	GTK_PANED(parent)->child1 = NULL;	gx->imhtml->parent = NULL;	gtk_paned_add1(GTK_PANED(parent), gx->imhtml);	g_object_unref(gx->imhtml);	gtk_widget_show_all(gx->imhtml);	gtk_widget_destroy(frame);}static gbooleanplugin_unload(PurplePlugin *plugin){	/* Restore the default ui-ops */	uiops->write_conv = default_write_conv;	uiops->create_conversation = default_create_conversation;	uiops->destroy_conversation = default_destroy_conversation;		/* Clear up everything */	g_hash_table_foreach(webkits, (GHFunc)remove_webkit, NULL);	g_hash_table_destroy(webkits);	return TRUE;}static PurplePluginInfo info ={	PURPLE_PLUGIN_MAGIC,		/* Magic				*/	PURPLE_MAJOR_VERSION,		/* Purple Major Version	*/	PURPLE_MINOR_VERSION,		/* Purple Minor Version	*/	PURPLE_PLUGIN_STANDARD,		/* plugin type			*/	PIDGIN_PLUGIN_TYPE,			/* ui requirement		*/	0,							/* flags				*/	NULL,						/* dependencies			*/	PURPLE_PRIORITY_DEFAULT,	/* priority				*/	PLUGIN_ID,					/* plugin id			*/	NULL,						/* name					*/	"0.1",					/* version				*/	NULL,						/* summary				*/	NULL,						/* description			*/	PLUGIN_AUTHOR,				/* author				*/	"http://pidgin.im",					/* website				*/	plugin_load,				/* load					*/	plugin_unload,				/* unload				*/	NULL,						/* destroy				*/	NULL,						/* ui_info				*/	NULL,						/* extra_info			*/	NULL,						/* prefs_info			*/	NULL,						/* actions				*/	NULL,						/* reserved 1			*/	NULL,						/* reserved 2			*/	NULL,						/* reserved 3			*/	NULL						/* reserved 4			*/};static voidinit_plugin(PurplePlugin *plugin) {	info.name = "Adium IMs";	info.summary = "Adium-like IMs with Pidgin";	info.description = "You can chat in Pidgin using Adium's WebKit view.";}PURPLE_INIT_PLUGIN(webkit, init_plugin, info)