pidgin/purple-plugin-pack

intelligent detection of which backend gtkspell is using. If there are some versions that are using enchant and not setting Libs.private, I'm not exactly interested, unless you can fix it _WITHOUT_ forcing the user to know and then tell configure
/* Message Splitter Plugin v0.95
*
* Splits a large message into smaller messages and sends them away
* in its place.
*
* Copyright (C) 2005-2007, Ike Gingerich <ike_@users.sourceforge.net>
*
* 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.
*/
#include "../common/pp_internal.h"
#include <pango/pango.h>
#ifndef _WIN32
# include <pango/pangocairo.h>
#endif
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <notify.h>
#include <version.h>
#include <util.h>
#define PLUGIN_ID "core-ike-splitter"
/* grr */
#ifndef ENOTCONN
#define ENOTCONN 107
#endif
/* plugin constants and structures */
static const gint MIN_SPLIT_SIZE = 32;
static const gint DEFAULT_SPLIT_SIZE = 786;
static const gint MAX_SPLIT_SIZE = 8192;
static const gint MIN_DELAY_MS = 0;
static const gint DEFAULT_DELAY_MS = 500;
static const gint MAX_DELAY_MS = 3600000;
typedef struct
{
char *sender_username;
char *sender_protocol_id;
GQueue *messages;
PurpleConversationType type;
union {
char *receiver; /* IM username */
gint id; /* chat ID */
};
} message_to_conv;
typedef struct {
gint start;
gint end;
} message_slice;
/* plugin preference variables */
static gint current_split_size;
/* initialize preferences dialog */
static PurplePluginPrefFrame *
get_plugin_pref_frame(PurplePlugin *plugin) {
PurplePluginPrefFrame *frame;
PurplePluginPref *ppref;
frame = purple_plugin_pref_frame_new();
g_return_val_if_fail(frame != NULL, NULL);
ppref = purple_plugin_pref_new_with_label("Message split size");
g_return_val_if_fail(ppref != NULL, NULL);
purple_plugin_pref_frame_add(frame, ppref);
ppref = purple_plugin_pref_new_with_name_and_label("/plugins/core/splitter/split_size",
"Letter count: ");
g_return_val_if_fail(ppref != NULL, NULL);
purple_plugin_pref_set_bounds(ppref, MIN_SPLIT_SIZE, MAX_SPLIT_SIZE);
purple_plugin_pref_frame_add(frame, ppref);
ppref = purple_plugin_pref_new_with_label("Delay between messages");
g_return_val_if_fail(ppref != NULL, NULL);
purple_plugin_pref_frame_add(frame, ppref);
ppref = purple_plugin_pref_new_with_name_and_label("/plugins/core/splitter/delay_ms",
"ms: ");
g_return_val_if_fail(ppref != NULL, NULL);
purple_plugin_pref_set_bounds(ppref, MIN_DELAY_MS, MAX_DELAY_MS);
purple_plugin_pref_frame_add(frame, ppref);
return frame;
}
/**
* A function to send a chat or im message to the specific conversation
* without emitting "sending-im" or "sending-chat" signal, which would
* cause an infinite loop for this plugin.
*
* taken from conversation.c with signal emission removed.
*/
static void
splitter_common_send(PurpleConversation *conv, const char *message,
PurpleMessageFlags msgflags)
{
PurpleConversationType type;
PurpleAccount *account;
PurpleConnection *gc;
char *displayed = NULL, *sent = NULL;
gint err = 0;
if (strlen(message) == 0)
return;
account = purple_conversation_get_account(conv);
gc = purple_conversation_get_gc(conv);
g_return_if_fail(account != NULL);
g_return_if_fail(gc != NULL);
type = purple_conversation_get_type(conv);
/* Always linkfy the text for display */
displayed = purple_markup_linkify(message);
if ((conv->features & PURPLE_CONNECTION_HTML) &&
!(msgflags & PURPLE_MESSAGE_RAW))
{
sent = g_strdup(displayed);
}
else
sent = g_strdup(message);
msgflags |= PURPLE_MESSAGE_SEND;
if (type == PURPLE_CONV_TYPE_IM) {
PurpleConvIm *im = PURPLE_CONV_IM(conv);
if (sent != NULL && sent[0] != '\0') {
err = serv_send_im(gc, purple_conversation_get_name(conv),
sent, msgflags);
if ((err > 0) && (displayed != NULL))
purple_conv_im_write(im, NULL, displayed, msgflags, time(NULL));
purple_signal_emit(purple_conversations_get_handle(), "sent-im-msg",
account,
purple_conversation_get_name(conv), sent);
}
}
else {
if (sent != NULL && sent[0] != '\0') {
err = serv_chat_send(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), sent, msgflags);
purple_signal_emit(purple_conversations_get_handle(), "sent-chat-msg",
account, sent,
purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)));
}
}
if (err < 0) {
const char *who;
const char *msg;
who = purple_conversation_get_name(conv);
if (err == -E2BIG) {
msg = _("Unable to send message: The message is too large.");
if (!purple_conv_present_error(who, account, msg)) {
char *msg2 = g_strdup_printf(_("Unable to send message to %s."), who);
purple_notify_error(gc, NULL, msg2, _("The message is too large."));
g_free(msg2);
}
}
else if (err == -ENOTCONN) {
purple_debug(PURPLE_DEBUG_ERROR, "conversation",
"Not yet connected.\n");
}
else {
msg = _("Unable to send message.");
if (!purple_conv_present_error(who, account, msg)) {
char *msg2 = g_strdup_printf(_("Unable to send message to %s."), who);
purple_notify_error(gc, NULL, msg2, NULL);
g_free(msg2);
}
}
}
g_free(displayed);
g_free(sent);
}
/* a timer based callback function that sends the next message in the queue */
static gboolean
send_message_timer_cb( message_to_conv *msg_to_conv ) {
PurpleAccount *account;
PurpleConversation *conv;
gchar *msg;
g_return_val_if_fail(msg_to_conv != NULL, FALSE);
g_return_val_if_fail(msg_to_conv->messages != NULL, FALSE);
g_return_val_if_fail(msg_to_conv->sender_username != NULL, FALSE);
g_return_val_if_fail(msg_to_conv->sender_protocol_id != NULL, FALSE);
msg = g_queue_pop_head(msg_to_conv->messages);
if( msg == NULL )
{
/* clean up and terminate timer callback */
g_queue_free(msg_to_conv->messages);
g_free(msg_to_conv->sender_username);
g_free(msg_to_conv->sender_protocol_id);
if( msg_to_conv->type == PURPLE_CONV_TYPE_IM &&
msg_to_conv->receiver != NULL )
g_free(msg_to_conv->receiver);
g_free(msg_to_conv);
return FALSE;
}
else
{
/* find account info (it may have changed) and try and create a new
conversation window (it may have been closed) or find the existing
chat, and finally send the message */
account = purple_accounts_find(msg_to_conv->sender_username,
msg_to_conv->sender_protocol_id);
g_return_val_if_fail(account != NULL, FALSE);
if( msg_to_conv->type == PURPLE_CONV_TYPE_IM && msg_to_conv->receiver != NULL )
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, msg_to_conv->receiver);
else if( msg_to_conv->type == PURPLE_CONV_TYPE_CHAT )
conv = purple_find_chat(account->gc, msg_to_conv->id);
else
conv = NULL;
g_return_val_if_fail(conv != NULL, FALSE);
splitter_common_send(conv, msg, PURPLE_MESSAGE_SEND);
g_free(msg);
return TRUE;
}
}
/* Create/get a pango context
*
* On windows we use the win32 context creator, on everything else we
* use the cairo one.
*/
PangoContext *
splitter_create_pango_context(void) {
#ifdef _WIN32
return pango_win32_get_context();
#else
PangoContext *context = NULL;
PangoFontMap *fontmap = pango_cairo_font_map_get_default();
context =
pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(fontmap));
g_object_unref(G_OBJECT(fontmap));
return context;
#endif
}
/* finds the first line-breakable character backwards starting from a[last] */
static gint
find_last_break(PangoLogAttr *a, int last) {
for(; last > 0; last-- )
if( a[last].is_line_break == 1)
break;
return ( a[last].is_line_break == 1) ? last-1 : -1;
}
/* uses Pango to find all possible line break locations in a message and returns
a PangoLogAttr array which maps to each byte of the message of length
one larger than the message. This must be g_free()'d */
static PangoLogAttr *
find_all_breaks(const char *message) {
PangoContext *context;
PangoLogAttr *a;
GList *list;
gint len, n_attr;
g_return_val_if_fail(message != NULL, NULL);
len = strlen(message);
n_attr = len+1;
a = g_new0(PangoLogAttr, n_attr);
/* init Pango */
context = splitter_create_pango_context();
g_return_val_if_fail(context != NULL, NULL);
list = pango_itemize(context, message, 0, len, NULL, NULL);
if (list != NULL && list->data != NULL)
pango_break(message, -1, &((PangoItem*)(list->data))->analysis, a, n_attr);
return a;
}
/* return a queue of message slices from a plain text message based on current_split_size using
Pango to determine possible line break locations */
static GQueue *
get_message_slices(const char *message) {
gint current_break_start, last_break_start, break_pos, len;
message_slice *slice;
PangoLogAttr *a;
GQueue *q;
q = g_queue_new();
len = strlen(message);
a = find_all_breaks(message);
g_return_val_if_fail(a != NULL, NULL);
last_break_start = current_break_start = 0;
while(current_break_start + current_split_size < len)
{
break_pos = find_last_break(a + last_break_start, current_split_size);
if( break_pos > -1 ) current_break_start += break_pos;
else current_break_start += current_split_size;
slice = g_new0( message_slice, 1 );
slice->start = MAX( last_break_start, 0 );
slice->end = MIN( current_break_start, len );
if( slice->end > slice->start )
g_queue_push_tail(q, slice);
else g_free(slice);
if( break_pos > -1 )
current_break_start++;
last_break_start = current_break_start;
}
slice = g_new0( message_slice, 1 );
slice->start = last_break_start;
slice->end = len;
g_queue_push_tail(q, slice);
/* cleanup */
g_free(a);
return q;
}
/* takes a message, splits it up based on whitespace (ignoring HTML formatting),
requests HTMLized slices of the splits, and returns a queue of them. The
messages and the queue must be freed */
static GQueue *
create_message_queue(const char *message) {
GQueue *slices, *messages;
message_slice *slice;
char *stripped_message, *msg;
gint stripped_len;
stripped_message = purple_markup_strip_html(message);
stripped_len = strlen(stripped_message);
messages = g_queue_new();
slices = get_message_slices(stripped_message);
g_return_val_if_fail(slices != NULL, NULL);
while( (slice = g_queue_pop_head(slices)) != NULL )
{
msg = purple_markup_slice(message, slice->start, slice->end);
if( msg != NULL )
g_queue_push_tail(messages, msg);
g_free(slice);
}
g_queue_free(slices);
/* cleanup */
g_free(stripped_message);
return messages;
}
/* create message queue and prepare timer callbacks */
static void
split_and_send(message_to_conv *msg_to_conv, const char **message) {
gint message_delay_ms;
g_return_if_fail( msg_to_conv != NULL );
g_return_if_fail( message != NULL );
g_return_if_fail( *message != NULL );
/* read and validate preferences */
current_split_size = purple_prefs_get_int("/plugins/core/splitter/split_size");
if( current_split_size > MAX_SPLIT_SIZE ) current_split_size = MAX_SPLIT_SIZE;
if( current_split_size < MIN_SPLIT_SIZE ) current_split_size = MIN_SPLIT_SIZE;
message_delay_ms = purple_prefs_get_int("/plugins/core/splitter/delay_ms");
if( message_delay_ms > MAX_DELAY_MS ) message_delay_ms = MAX_DELAY_MS;
if( message_delay_ms < MIN_DELAY_MS ) message_delay_ms = MIN_DELAY_MS;
/* prepare message queue */
msg_to_conv->messages = create_message_queue(*message);
g_return_if_fail( msg_to_conv->messages != NULL );
/* initialize message send timer */
purple_timeout_add( (g_queue_get_length(msg_to_conv->messages) > 1) ? message_delay_ms : 0,
(GSourceFunc)send_message_timer_cb,
msg_to_conv);
/* free the original message and ensure it does not get sent */
g_free((char*)*message);
*message = NULL;
}
/* initialize a chat message to potentially be split */
static void
sending_chat_msg_cb(PurpleAccount *account, const char **message, int id) {
message_to_conv *msg_to_conv;
purple_debug(PURPLE_DEBUG_MISC, "purple-splitter", "splitter plugin invoked\n");
g_return_if_fail(account != NULL);
g_return_if_fail(message != NULL);
g_return_if_fail(*message != NULL);
msg_to_conv = g_new0(message_to_conv, 1);
g_return_if_fail( msg_to_conv != NULL );
msg_to_conv->sender_username = g_strdup(account->username);
msg_to_conv->sender_protocol_id = g_strdup(account->protocol_id);
msg_to_conv->id = id;
msg_to_conv->type = PURPLE_CONV_TYPE_CHAT;
split_and_send(msg_to_conv, message);
}
/* initialize an IM message to potentially be split */
static void
sending_im_msg_cb(PurpleAccount *account, const char *receiver,
const char **message)
{
message_to_conv *msg_to_conv;
purple_debug(PURPLE_DEBUG_MISC, "purple-splitter", "splitter plugin invoked\n");
g_return_if_fail(account != NULL);
g_return_if_fail(receiver != NULL);
g_return_if_fail(message != NULL);
g_return_if_fail(*message != NULL);
msg_to_conv = g_new0(message_to_conv, 1);
g_return_if_fail( msg_to_conv != NULL );
msg_to_conv->sender_username = g_strdup(account->username);
msg_to_conv->sender_protocol_id = g_strdup(account->protocol_id);
msg_to_conv->receiver = g_strdup(receiver);
msg_to_conv->type = PURPLE_CONV_TYPE_IM;
split_and_send(msg_to_conv, message);
}
/* register "sending" message signal callback */
static gboolean
plugin_load(PurplePlugin *plugin) {
purple_signal_connect(purple_conversations_get_handle(),
"sending-im-msg",
plugin,
PURPLE_CALLBACK(sending_im_msg_cb),
NULL);
purple_signal_connect(purple_conversations_get_handle(),
"sending-chat-msg",
plugin,
PURPLE_CALLBACK(sending_chat_msg_cb),
NULL);
return TRUE;
}
static gboolean
plugin_unload(PurplePlugin *plugin) {
return TRUE;
}
static PurplePluginUiInfo prefs_info = {
get_plugin_pref_frame, 0, NULL, NULL, NULL, NULL, NULL
};
static PurplePluginInfo info = {
PURPLE_PLUGIN_MAGIC,
PURPLE_MAJOR_VERSION,
PURPLE_MINOR_VERSION,
PURPLE_PLUGIN_STANDARD,
NULL,
0,
NULL,
PURPLE_PRIORITY_DEFAULT,
PLUGIN_ID,
NULL,
PP_VERSION,
NULL,
NULL,
"Ike Gingerich <ike_@users.sourceforge.net>",
PP_WEBSITE,
plugin_load,
plugin_unload,
NULL,
NULL,
NULL,
&prefs_info,
NULL,
NULL,
NULL,
NULL,
NULL
};
/* store initial preference values */
static void
init_plugin(PurplePlugin *plugin) {
#ifdef ENABLE_NLS
bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */
info.name = _("Message Splitter");
info.summary = _("Splits a large outgoing message into smaller messages of "
"a specified size.");
info.description = info.summary;
purple_prefs_add_none("/plugins/core/splitter");
purple_prefs_add_int ("/plugins/core/splitter/split_size", DEFAULT_SPLIT_SIZE);
purple_prefs_add_int ("/plugins/core/splitter/delay_ms", DEFAULT_DELAY_MS);
}
PURPLE_INIT_PLUGIN(splitter, init_plugin, info)