Conversion from plugin to within prpl actually compiles. Most of old
(usable) code is commented out. Functions for sending and recieving Doodle
packets are active and work. Purposely broken 'doodle_session' code crashes.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Makefile.am Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,268 @@
+ win32/IdleTracker/Makefile.mingw \ + win32/IdleTracker/idletrack.c \ + win32/IdleTracker/idletrack.h \ + win32/MinimizeToTray.c \ + win32/MinimizeToTray.h \ + win32/libc_interface.c \ + win32/libc_interface.h \ + win32/libc_internal.h \ + win32/mingw_plus/winuser_extra.h \ + win32/nsis/gaim-header.bmp \ + win32/nsis/gaim-intro.bmp \ + win32/nsis/langmacros.nsh \ + win32/nsis/translations/albanian.nsh \ + win32/nsis/translations/bulgarian.nsh \ + win32/nsis/translations/catalan.nsh \ + win32/nsis/translations/czech.nsh \ + win32/nsis/translations/danish.nsh \ + win32/nsis/translations/dutch.nsh \ + win32/nsis/translations/english.nsh \ + win32/nsis/translations/finnish.nsh \ + win32/nsis/translations/french.nsh \ + win32/nsis/translations/german.nsh \ + win32/nsis/translations/hebrew.nsh \ + win32/nsis/translations/hungarian.nsh \ + win32/nsis/translations/italian.nsh \ + win32/nsis/translations/japanese.nsh \ + win32/nsis/translations/korean.nsh \ + win32/nsis/translations/norwegian.nsh \ + win32/nsis/translations/polish.nsh \ + win32/nsis/translations/portuguese.nsh \ + win32/nsis/translations/portuguese-br.nsh \ + win32/nsis/translations/romanian.nsh \ + win32/nsis/translations/russian.nsh \ + win32/nsis/translations/serbian-latin.nsh \ + win32/nsis/translations/simp-chinese.nsh \ + win32/nsis/translations/slovak.nsh \ + win32/nsis/translations/slovenian.nsh \ + win32/nsis/translations/spanish.nsh \ + win32/nsis/translations/swedish.nsh \ + win32/nsis/translations/trad-chinese.nsh \ + win32/nsis/translations/vietnamese.nsh +bin_PROGRAMS = gaim gaim-remote + gtkcellviewmenuitem.c \ + gtkcellrendererprogress.c \ + gtkcellviewmenuitem.h \ + gtkcellviewmenuitem.h \ + gtkcellrendererprogress.h \ +gaimincludedir=$(includedir)/gaim +gaim_DEPENDENCIES = @LIBOBJS@ $(STATIC_LINK_LIBS) +gaim_LDFLAGS = -export-dynamic + $(STARTUP_NOTIFICATION_LIBS) +gaim_remote_DEPENDENCIES = @LIBOBJS@ + @LIBOBJS@ $(GLIB_LIBS) $(INTLLIBS) \ + $(top_builddir)/plugins/gaim-remote/libgaim-remote.la + -DDATADIR=\"$(datadir)\" \ + -DLIBDIR=\"$(libdir)/gaim/\" \ + -DLOCALEDIR=\"$(datadir)/locale\" \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -I$(top_srcdir)/plugins \ + $(STARTUP_NOTIFICATION_CFLAGS) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkwhiteboard.c Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,23 @@
+ * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkwhiteboard.h Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,24 @@
+ * @file gtkwhiteboard.h The GtkGaimWhiteboard frontend object + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 Binary file src/gtkwhiteboard.o has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/Makefile.am Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,51 @@
+libyahoo_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) +noinst_LIBRARIES = libyahoo.a +libyahoo_a_SOURCES = $(YAHOOSOURCES) +libyahoo_a_CFLAGS = $(AM_CFLAGS) +pkg_LTLIBRARIES = libyahoo.la +libyahoo_la_SOURCES = $(YAHOOSOURCES) --- a/src/protocols/yahoo/yahoo.c Tue Jul 12 18:30:55 2005 -0400
+++ b/src/protocols/yahoo/yahoo.c Wed Jul 13 22:02:56 2005 -0400
@@ -45,6 +45,7 @@
#include "yahoo_filexfer.h"
+#include "yahoo_doodle.h" #include "yahoo_picture.h"
extern char *yahoo_crypt(const char *, const char *);
@@ -2017,6 +2018,7 @@
yahoo_process_stealth(gc, pkt);
case YAHOO_SERVICE_P2PFILEXFER:
+ yahoo_process_p2pfilexfer( gc, pkt ); // This case had no break and continued; thus keeping it this way. case YAHOO_SERVICE_FILETRANSFER:
yahoo_process_filetransfer(gc, pkt);
@@ -2912,6 +2914,19 @@
yahoo_packet_hash_str(pkt, 97, "1");
yahoo_packet_hash_str(pkt, 14, msg2);
+ // If this message is to a user who is also Doodling with the local user, + // format the chat packet with the IMV information (thanks Yahoo!) + doodle_session *ds = goodle_get_doodle_session( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + yahoo_packet_hash(pkt, 63, "doodle;11"); + yahoo_packet_hash(pkt, 63, ";0"); // IMvironment yahoo_packet_hash_str(pkt, 63, ";0"); /* IMvironment */
yahoo_packet_hash_str(pkt, 64, "0"); /* no idea */
yahoo_packet_hash_str(pkt, 1002, "1"); /* no idea, Yahoo 6 or later only it seems */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo_doodle.c Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,1558 @@
+ * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 +// INCLUDES ============================================================================================ +#include "yahoo_packet.h" +#include "yahoo_friend.h" +#include "yahoo_filexfer.h" +#include "yahoo_picture.h" +#include "yahoo_doodle.h" +// GLOBALS ============================================================================================= +gboolean auto_accept = TRUE; +GList *buttonList = NULL; +int LastX; // Tracks last position of the mouse when drawing +int MotionCount; // Tracks how many brush motions made +int BrushState = BRUSH_STATE_UP; +const int DefaultColorRGB24[] = +//GdkColor DefaultColor[PALETTE_NUM_OF_COLORS]; +// FUNCTIONS ============================================================================================ +// This function is called at the start of Gaim (twice?) +// Anyhow... this is the biggy... I think :P +void init_plugin( GaimPlugin *plugin ) + for( i = 0; i < PALETTE_NUM_OF_COLORS; i++ ) + goodle_rgb24_to_rgb48( DefaultColorRGB24[i], &DefaultColor[i] ); +// ------------------------------------------------------------------------------------------------------ +// plugin_load is not required. It is called when the plugin is loaded so that you can +// initialize any variables and so on. Therefore, called when selected in Preferences->Plugins +gboolean goodle_load( GaimPlugin *plugin ) + // Set up signal/functions for when connections are made/ended + void *con_handle = gaim_connections_get_handle(); + gaim_signal_connect( con_handle, "signed-on", plugin, + GAIM_CALLBACK( goodle_con_signed_on ), NULL ); + gaim_signal_connect( con_handle, "signed-off", plugin, + GAIM_CALLBACK( goodle_con_signed_off ), NULL ); + // Set up signal/functions for when the conversation/windows are created + void *conv_handle = gaim_conversations_get_handle(); + gaim_signal_connect( conv_handle, "conversation-created", plugin, + GAIM_CALLBACK( goodle_conv_created ), NULL ); + gaim_signal_connect( conv_handle, "deleting-conversation", plugin, + GAIM_CALLBACK( goodle_conv_destroyed ), NULL ); + // Make all active Yahoo accounts use the Goodle network packet handler + goodle_set_goodle_functions( TRUE ); +// ------------------------------------------------------------------------------------------------------ +// plugin_destroy is called when plugin is unloaded (unselected) +gboolean goodle_unload( GaimPlugin *plugin ) + // Destroy any buttons that haven't been destroyed from closing conversation windows + g_list_free( buttonList ); + // Clear the list of Doodle sessions (TODO also make sure the pixmaps and perhaps widgets are destroyed too) + //struct doodle_session *ds; +// // Check if a Doodle session exists with this user +// //g_print( "%s\n", ds->who ); + // Return full packet handling to the original Yahoo plugin + goodle_set_goodle_functions( FALSE ); +// ------------------------------------------------------------------------------------------------------ +// plugin_destroy is called when plugin is destroyed (fails/crashes) +void goodle_destroy( GaimPlugin *plugin ) + g_list_free( buttonList ); +// ------------------------------------------------------------------------------------------------------ +void goodle_con_signed_on( GaimConnection *gc, gpointer data ) + g_print( "%s -> SIGNED ON\n", gaim_account_get_username( gc->account ) ); + goodle_set_goodle_functions( TRUE ); +// ------------------------------------------------------------------------------------------------------ +void goodle_con_signed_off( GaimConnection *gc, gpointer data ) + g_print( "%s -> SIGNED OFF\n", gaim_account_get_username( gc->account ) ); +// ------------------------------------------------------------------------------------------------------ +void goodle_conv_created( GaimConversation *conv, gpointer data ) + GaimGtkConversation *gtkconv; + //GaimConversationType type; + // Check if this is a valid conversation window in Gaim ? + if( ( gtkconv = GAIM_GTK_CONVERSATION( conv ) ) == NULL ) + //type = gaim_conversation_get_type( conv ); + // Create this button (via Gaim API) + button = gaim_gtkconv_button_new( NULL, + "Goodle", "Start a Doodle session with this user", + ( gpointer )( conv ) ); + // Stamp this button with essentially information attributing it to this particular conversation? + g_object_set_data( G_OBJECT( button ), "conv", conv ); + // Add this particular button to the global list of buttons + buttonList = g_list_append( buttonList, ( gpointer )( button ) ); + // Place the button at the bottom of the Gaim conversation window + gtk_box_pack_end( GTK_BOX( gtkconv->bbox ), button, TRUE, TRUE, 0 ); + gtk_size_group_add_widget( gtkconv->sg, button ); + gtk_widget_show( button ); +// ------------------------------------------------------------------------------------------------------ +void goodle_conv_destroyed( GaimConversation *conv, gpointer data ) + GaimConversation *stored_conv; + // Traverse the list of buttons and destroy the one associated with this conversation window + for( l = buttonList; l != NULL; l = l_next ) + button = GTK_WIDGET( l->data ); + stored_conv = ( GaimConversation* )g_object_get_data( G_OBJECT( button ), "conv" ); + // Does this button's 'stamp' match that of this conversation? + if( stored_conv == conv ) + gtk_widget_destroy( button ); + buttonList = g_list_remove( buttonList, l->data ); +// ------------------------------------------------------------------------------------------------------ +void goodle_button_press( GtkButton *button, gpointer data ) + GaimConversation *conv = data; + GaimAccount *account = gaim_conversation_get_account( conv ); + GaimConnection *gc = gaim_account_get_connection( account ); + char *to = ( char* )( gaim_conversation_get_name( conv ) ); + // Get the doodle session + doodle_session *ds = yahoo_doodle_session_get( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + // Write a local message to this conversation showing that + // a request for a Doodle session has been made + gaim_conv_im_write( GAIM_CONV_IM( conv ), "", _("Sent Doodle request."), + GAIM_MESSAGE_NICK | GAIM_MESSAGE_RECV, time( NULL ) ); + goodle_send_command_request( gc, to ); + yahoo_doodle_command_send_ready( gc, to ); + // Insert this 'session' in the list. At this point, it's only a requested session. + ds = g_new0( doodle_session, 1 ); + ds->state = DOODLE_STATE_REQUESTING; + dsList = g_list_append( dsList, ( gpointer )( ds ) ); +// ------------------------------------------------------------------------------------------------------ +// Configuration dialog for plugin (uses GTK) +GtkWidget *get_config_frame( GaimPlugin *plugin ) + GtkWidget *vbox, *frame; + vbox = gtk_vbox_new( FALSE, 6 ); + gtk_container_set_border_width( GTK_CONTAINER( vbox ), 12 ); + frame = gaim_gtk_make_frame( vbox, "Settings" ); + gaim_gtk_prefs_checkbox( "Automatically accept Doodle requests", + "/plugins/gtk/goodle/auto_accept", + gtk_widget_show_all( vbox ); +// ------------------------------------------------------------------------------------------------------ +void goodle_set_goodle_functions( gboolean useGoodle ) + GList *accountList = NULL; + // Obtain all the Gaim accounts (no other way to get to the GaimConnections?) + accountList = gaim_accounts_get_all(); + // Traverse through the accounts to determine which ones are Yahoo + for( l = accountList; l != NULL; l = l_next ) + account = ( GaimAccount* )( l->data ); + // Check if this particular account is Yahoo + if( !strcmp( gaim_account_get_protocol_id( account ), "prpl-yahoo" ) ) + // See if this Yahoo account is active + if( gaim_account_is_connected( account ) ) + gc = gaim_account_get_connection( account ); + GaimPlugin *gp = gaim_find_prpl( "prpl-yahoo" ); + GaimPluginInfo *gpi = gp->info; + GaimPluginProtocolInfo *gppi = gpi->extra_info; + // Replace the packet handler function with a modified one + // to check for packets concerning 'Doodle' + gaim_input_remove( gc->inpa ); + gc->inpa = gaim_input_add( yd->fd, GAIM_INPUT_READ, goodle_pending, gc ); + // Replace the 'send_im' function with a modified one + // to check to see if we are chatting with a person we are doodling with + gppi->send_im = goodle_send_im; + // Replace the modified packet handler function with original + gaim_input_remove( gc->inpa ); + gc->inpa = gaim_input_add( yd->fd, GAIM_INPUT_READ, yahoo_pending, gc ); + // Replace the modified 'send_im' with the original + gppi->send_im = yahoo_send_im; + // Clear our list of accounts? +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_process( GaimConnection *gc, char *me, char *from, char *command, char *message ) +// g_print( "-----------------------------------------------\n" ); +// g_print( "%s : %s : %s -> %s\n", from, imv, command, message ); + // Now check to see what sort of Doodle message it is + int cmd = atoi( command ); + case DOODLE_CMD_REQUEST: + yahoo_doodle_command_got_request( gc, from ); + yahoo_doodle_command_got_ready( gc, from ); + yahoo_doodle_command_got_clear( gc, from ); + yahoo_doodle_command_got_draw( gc, from, message ); + yahoo_doodle_command_got_extra( gc, from, message ); + case DOODLE_CMD_CONFIRM: + yahoo_doodle_command_got_confirm( gc, from ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_request( GaimConnection *gc, char *from ) + char *me = ( char* )( gaim_account_get_username( gc->account ) ); + // Only handle this if local client requested Doodle session (else local client would have sent one) + doodle_session *ds = yahoo_doodle_session_get( dsList, me, from ); + g_print( "-----------------------------------------------\n" ); + g_print( "Got REQUEST (%s)\n", from ); + // If a session with the remote user doesn't exist + // Ask user if he/she wishes to accept the request for a doodle session + // TODO Ask local user to start Doodle session with remote user + // NOTE This if/else statement won't work right--must use dialog results + /* char dialog_message[64]; + g_sprintf( dialog_message, "%s is requesting to start a Doodle session with you.", from ); + gaim_notify_message( NULL, GAIM_NOTIFY_MSG_INFO, "Doodle", + dialog_message, NULL, NULL, NULL ); + ds = g_new0( doodle_session, 1 ); + strcpy( ds->who, from ); + ds->state = DOODLE_STATE_REQUESTED; + dsList = g_list_append( dsList, ( gpointer )( ds ) ); + yahoo_doodle_command_send_request( gc, from ); + // TODO Might be required to clear the canvas of an existing doodle session at this point +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_ready( GaimConnection *gc, char *from ) + // Only handle this if local client requested Doodle session (else local client would have sent one) + doodle_session *ds = yahoo_doodle_session_get( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + g_print( "-----------------------------------------------\n" ); + g_print( "Got READY (%s)\n", from ); + if( ds->state == DOODLE_STATE_REQUESTING ) + // TODO Check for active pixmap? + //goodle_doodle_session_start( ds ); + ds->state = DOODLE_STATE_ESTABLISHED; + yahoo_doodle_command_send_confirm( gc, from ); + if( ds->state == DOODLE_STATE_ESTABLISHED ) + // TODO Call clear function (requires function to clear canvas (but not send clear command packet) + // Ask whether to save picture too + //goodle_doodle_session_clear_canvas( ds ); + //goodle_doodle_session_set_canvas_as_icon( ds ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_draw( GaimConnection *gc, char *from, char *message ) + // Only handle this if local client requested Doodle session (else local client would have sent one) + doodle_session *ds = yahoo_doodle_session_get( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + g_print( "-----------------------------------------------\n" ); + g_print( "Got DRAW (%s)\n", from ); + g_print( "Draw Message: %s\n", message ); + // Convert drawing packet message to an integer list + int length = strlen( message ); + GList *d_list = NULL; // a local list of drawing info + // Check to see if the message begans and ends with quotes + if( ( message[0] != '\"' ) || ( message[length - 1] != '\"' ) ) + // Truncate the quotations off of our message (why the hell did they add them anyways!?) + message[length - 1] = ','; + // Traverse and extract all integers divided by commas + while( ( token_end = strchr( message, ',' ) ) ) + token = g_new0( int, 1 ); + *token = atoi( message ); + d_list = g_list_append( d_list, ( gpointer )( token ) ); + message = token_end + 1; + //goodle_doodle_draw_stroke( ds, d_list ); + //goodle_doodle_session_set_canvas_as_icon( ds ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_clear( GaimConnection *gc, char *from ) + // Only handle this if local client requested Doodle session (else local client would have sent one) + doodle_session *ds = yahoo_doodle_session_get( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + g_print( "-----------------------------------------------\n" ); + g_print( "Got CLEAR (%s)\n", from ); + if( ds->state == DOODLE_STATE_ESTABLISHED ) + // TODO Ask user whether to save the image before clearing it + //goodle_doodle_session_clear_canvas( ds ); + //goodle_doodle_session_set_canvas_as_icon( ds ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_extra( GaimConnection *gc, char *from, char *message ) + // I do not like these 'extra' features, so I'll only handle them in one way, + // which is returning them with the command/packet to turn them off + g_print( "-----------------------------------------------\n" ); + g_print( "Got EXTRA (%s)\n", from ); + yahoo_doodle_command_send_extra( gc, from, DOODLE_EXTRA_NONE ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_confirm( GaimConnection *gc, char *from ) + // Get the doodle session + doodle_session *ds = yahoo_doodle_session_get( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + g_print( "-----------------------------------------------\n" ); + g_print( "Got CONFIRM (%s)\n", from ); + // TODO Combine the following IF's? + // Check if we requested a doodle session + if( ds->state == DOODLE_STATE_REQUESTING ) + ds->state = DOODLE_STATE_ESTABLISHED; + // Check if this session wasn't already started before we start it (again?) + //if( ds->pixmap == NULL ) + // goodle_doodle_session_start( ds ); + yahoo_doodle_command_send_confirm( gc, from ); + // Check if we accepted a request for a doodle session + if( ds->state == DOODLE_STATE_REQUESTED ) + ds->state = DOODLE_STATE_ESTABLISHED; + // Check if this session wasn't already started before we start it (again?) + //if( ds->pixmap == NULL ) + // goodle_doodle_session_start( ds ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_got_shutdown( GaimConnection *gc, char *from ) + // Get the doodle session + doodle_session *ds = yahoo_doodle_session_get( dsList, + ( char* )( gaim_account_get_username( gc->account ) ), + g_print( "-----------------------------------------------\n" ); + g_print( "Got SHUTDOWN (%s)\n", from ); + // TODO Ask if user wants to save picture before the session is closed + // If this session doesn't exist, don't try and kill it + ;//gtk_widget_destroy( ds->window ); +// ------------------------------------------------------------------------------------------------------ +int yahoo_doodle_command_send_request( GaimConnection *gc, char *to ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent REQUEST (%s)\n", to ); + // Make and send an acknowledge (ready) Doodle packet + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) ); + yahoo_packet_hash( pkt, 14, "1" ); + yahoo_packet_hash( pkt, 13, "1" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, "doodle;11" ); + yahoo_packet_hash( pkt, 64, "1" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +int yahoo_doodle_command_send_ready( GaimConnection *gc, char *to ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent READY (%s)\n", to ); + // Make and send a request to start a Doodle session + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) ); + yahoo_packet_hash( pkt, 14, "" ); + yahoo_packet_hash( pkt, 13, "0" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, "doodle;11" ); + yahoo_packet_hash( pkt, 64, "0" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_send_draw( GaimConnection *gc, char *to, char *message ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent DRAW (%s)\n", to ); + // Make and send a drawing packet + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) ); + yahoo_packet_hash( pkt, 14, message ); + yahoo_packet_hash( pkt, 13, "3" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, "doodle;11" ); + yahoo_packet_hash( pkt, 64, "1" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_send_clear( GaimConnection *gc, char *to ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent CLEAR (%s)\n", to ); + // Make and send a request to clear packet + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) ); + yahoo_packet_hash( pkt, 14, " " ); + yahoo_packet_hash( pkt, 13, "2" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, "doodle;11" ); + yahoo_packet_hash( pkt, 64, "1" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_send_extra( GaimConnection *gc, char *to, char *message ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent EXTRA (%s)\n", to ); + // Send out a request to use a specified 'extra' feature (message) + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) ); + yahoo_packet_hash( pkt, 14, message ); + yahoo_packet_hash( pkt, 13, "4" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, "doodle;11" ); + yahoo_packet_hash( pkt, 64, "1" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_send_confirm( GaimConnection *gc, char *to ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent CONFIRM (%s)\n", to ); + // Send ready packet (that local client accepted and is ready) + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, ( char* )( gaim_account_get_username( gc->account ) ) ); + yahoo_packet_hash( pkt, 14, "1" ); + yahoo_packet_hash( pkt, 13, "5" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, "doodle;11" ); + yahoo_packet_hash( pkt, 64, "1" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +void yahoo_doodle_command_send_shutdown( GaimConnection *gc, char *to ) + struct yahoo_packet *pkt; + g_print( "-----------------------------------------------\n" ); + g_print( "Sent SHUTDOWN (%s)\n", to ); + // Declare that you are ending the Doodle session + pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 ); + yahoo_packet_hash( pkt, 49, "IMVIRONMENT" ); + yahoo_packet_hash( pkt, 1, gaim_account_get_username( gc->account ) ); + yahoo_packet_hash( pkt, 14, "" ); + yahoo_packet_hash( pkt, 13, "0" ); + yahoo_packet_hash( pkt, 5, to ); + yahoo_packet_hash( pkt, 63, ";0" ); + yahoo_packet_hash( pkt, 64, "0" ); + yahoo_packet_hash( pkt, 1002, "1" ); + yahoo_packet_send( yd, pkt ); + yahoo_packet_free( pkt ); +// ------------------------------------------------------------------------------------------------------ +// Looks through a specified list of Doodle sessions for one that is between usernames 'me' and 'who' +// Returns a pointer to a matching doodle_session; if none match, it returns NULL +doodle_session *yahoo_doodle_session_get( GList *ds_list, char *me, char *who ) + doodle_session *ds = NULL; + // Look for a Doodle session between the local user and the remote user + if( !strcmp( gaim_account_get_username( ds->gc->account ), me ) && !strcmp( ds->who, who ) ) +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_start( doodle_session *ds ) + GtkWidget *drawing_area; + GtkWidget *hbox_palette; + GtkWidget *vbox_palette_above_canvas_and_controls; + GtkWidget *hbox_canvas_and_controls; + GtkWidget *vbox_controls; + // -------------------------- + // |[][][][palette[][][][][]| + // |------------------------| + // -------------------------- + GtkWidget *clear_button; + GtkWidget *save_button; + GtkWidget *color_box[PALETTE_NUM_OF_COLORS]; + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_widget_set_name( window, ds->who ); + gtk_window_set_title( ( GtkWindow* )( window ), ds->who ); // TODO Try and use alias first + gtk_window_set_resizable( ( GtkWindow* )( window ), FALSE ); + g_signal_connect( G_OBJECT( window ), "destroy", + G_CALLBACK( goodle_doodle_session_end ), ( gpointer )( ds ) ); + // Create vertical box to place palette above the canvas and controls + vbox_palette_above_canvas_and_controls = gtk_vbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( window ), vbox_palette_above_canvas_and_controls ); + gtk_widget_show( vbox_palette_above_canvas_and_controls ); + // Create horizontal box for the palette and all its entries + hbox_palette = gtk_hbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( vbox_palette_above_canvas_and_controls ), hbox_palette ); + gtk_widget_show( hbox_palette ); + // Create horizontal box to seperate the canvas from the controls + hbox_canvas_and_controls = gtk_hbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( vbox_palette_above_canvas_and_controls ), hbox_canvas_and_controls ); + gtk_widget_show( hbox_canvas_and_controls ); + for( i = 0; i < PALETTE_NUM_OF_COLORS; i++ ) + color_box[i] = gtk_label_new( NULL ); + gtk_widget_set_size_request( color_box[i], DOODLE_CANVAS_WIDTH / PALETTE_NUM_OF_COLORS ,32 ); + gtk_container_add( GTK_CONTAINER( hbox_palette ), color_box[i] ); + gtk_widget_show( color_box[i] ); + // Create the drawing area + drawing_area = gtk_drawing_area_new(); + ds->drawing_area = drawing_area; + gtk_widget_set_size_request( GTK_WIDGET( drawing_area ), DOODLE_CANVAS_WIDTH, DOODLE_CANVAS_HEIGHT ); + gtk_box_pack_start( GTK_BOX( hbox_canvas_and_controls ), drawing_area, TRUE, TRUE, 8 ); + gtk_widget_show( drawing_area ); + // Signals used to handle backing pixmap + g_signal_connect( G_OBJECT( drawing_area ), "expose_event", + G_CALLBACK( goodle_doodle_session_expose_event ), ( gpointer )( ds ) ); + g_signal_connect( G_OBJECT( drawing_area ), "configure_event", + G_CALLBACK( goodle_doodle_session_configure_event ), ( gpointer )( ds ) ); + g_signal_connect( G_OBJECT( drawing_area ), "button_press_event", + G_CALLBACK( goodle_doodle_session_brush_down ), ( gpointer )( ds ) ); + g_signal_connect( G_OBJECT( drawing_area ), "motion_notify_event", + G_CALLBACK( goodle_doodle_session_brush_motion ), ( gpointer )( ds ) ); + g_signal_connect( G_OBJECT( drawing_area ), "button_release_event", + G_CALLBACK( goodle_doodle_session_brush_up ), ( gpointer )( ds ) ); + gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_HINT_MASK ); + // Create vertical box to contain the controls + vbox_controls = gtk_vbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER( hbox_canvas_and_controls ), vbox_controls ); + gtk_widget_show( vbox_controls ); + clear_button = gtk_button_new_with_label( "Clear" ); + gtk_widget_set_size_request( clear_button, 96 ,32 ); + gtk_box_pack_start( GTK_BOX( vbox_controls ), clear_button, FALSE, FALSE, 0 ); + gtk_widget_show( clear_button ); + g_signal_connect( G_OBJECT( clear_button ), "clicked", + G_CALLBACK( goodle_doodle_session_button_clear_press ), ( gpointer )( ds ) ); + save_button = gtk_button_new_with_label( "Save" ); + gtk_widget_set_size_request( save_button, 96 ,32 ); + gtk_box_pack_start( GTK_BOX( vbox_controls ), save_button, FALSE, FALSE, 8 ); + gtk_widget_show( save_button ); + // Make all this (window) visible + gtk_widget_show( window ); + goodle_doodle_session_set_canvas_as_icon( ds ); + // Set default brush size and color + ds->brush_size = DOODLE_BRUSH_MEDIUM; + ds->brush_color = 0; // black +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_end( GtkWidget *widget, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + GaimConnection *gc = ds->gc; + GdkPixmap *pixmap = ds->pixmap; + //g_print( "goodle_doodle_session_end | %s\n", ds->who ); + // TODO Ask if user wants to save picture before the session is closed + if( ds == NULL ) // Fixes sessions with yourself? + yahoo_doodle_command_send_shutdown( gc, ds->who ); + // Clear pixmap memory before we remove this session from the Doodle session list + g_object_unref( pixmap ); + dsList = g_list_remove( dsList, ds ); +// ------------------------------------------------------------------------------------------------------ +gboolean goodle_doodle_session_configure_event( GtkWidget *widget, GdkEventConfigure *event, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + GdkPixmap *pixmap = ds->pixmap; + //g_print( "goodle_doodle_session_configure_event | %s\n", ds->who ); + g_object_unref( pixmap ); + pixmap = gdk_pixmap_new( widget->window, + widget->allocation.width, + widget->allocation.height, + gdk_draw_rectangle( pixmap, + widget->style->white_gc, + widget->allocation.width, + widget->allocation.height ); +// ------------------------------------------------------------------------------------------------------ +gboolean goodle_doodle_session_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + GdkPixmap *pixmap = ds->pixmap; + //g_print( "goodle_doodle_session_expose_event | %s\n", ds->who ); + gdk_draw_drawable( widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE( widget )], + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height ); +// ------------------------------------------------------------------------------------------------------ +gboolean goodle_doodle_session_brush_down( GtkWidget *widget, GdkEventButton *event, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + GdkPixmap *pixmap = ds->pixmap; + int *brush_color = NULL; + int *brush_size = NULL; + if( BrushState != BRUSH_STATE_UP ) + // Potential double-click DOWN to DOWN? + g_print( "***Bad brush state transition %d to DOWN\n", BrushState ); + BrushState = BRUSH_STATE_DOWN; + BrushState = BRUSH_STATE_DOWN; + //g_print( "BRUSH_DOWN | %s\n", ds->who ); + if( event->button == 1 && pixmap != NULL ) + // Check if drawList has contents; if so, clear it + goodle_doodle_session_destroy_draw_list( drawList ); + brush_color = g_new0( int, 1 ); + brush_size = g_new0( int, 1 ); + *brush_color = ds->brush_color; + *brush_size = ds->brush_size; + // Set tracking variables + drawList = g_list_append( drawList, ( gpointer )( brush_color ) ); + drawList = g_list_append( drawList, ( gpointer )( brush_size ) ); + drawList = g_list_append( drawList, ( gpointer )( x0 ) ); + drawList = g_list_append( drawList, ( gpointer )( y0 ) ); + goodle_doodle_session_draw_brush_point( widget, ds, + ds->brush_color, ds->brush_size ); +// ------------------------------------------------------------------------------------------------------ +gboolean goodle_doodle_session_brush_motion( GtkWidget *widget, GdkEventMotion *event, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + GdkPixmap *pixmap = ds->pixmap; + //g_print( "BRUSH_MOTION | %s\n", ds->who ); + gdk_window_get_pointer( event->window, &x, &y, &state ); + if( state & GDK_BUTTON1_MASK && pixmap != NULL ) + if( ( BrushState != BRUSH_STATE_DOWN ) && ( BrushState != BRUSH_STATE_MOTION ) ) + g_print( "***Bad brush state transition %d to MOTION\n", BrushState ); + BrushState = BRUSH_STATE_MOTION; + BrushState = BRUSH_STATE_MOTION; + if( MotionCount == DOODLE_MAX_BRUSH_MOTIONS ) + drawList = g_list_append( drawList, ( gpointer )( dx ) ); + drawList = g_list_append( drawList, ( gpointer )( dy ) ); + char* message = goodle_doodle_session_build_draw_string( drawList ); + yahoo_doodle_command_send_draw( ds->gc, ds->who, message ); + // The brush stroke is finished, clear the list for another one + goodle_doodle_session_destroy_draw_list( drawList ); + int *brush_color = g_new0( int, 1 ); + int *brush_size = g_new0( int, 1 ); + int *x0 = g_new0( int, 1 ); + int *y0 = g_new0( int, 1 ); + *brush_color = ds->brush_color; + *brush_size = ds->brush_size; + // Reset motion tracking + drawList = g_list_append( drawList, ( gpointer )( brush_color ) ); + drawList = g_list_append( drawList, ( gpointer )( brush_size ) ); + drawList = g_list_append( drawList, ( gpointer )( x0 ) ); + drawList = g_list_append( drawList, ( gpointer )( y0 ) ); + drawList = g_list_append( drawList, ( gpointer )( dx ) ); + drawList = g_list_append( drawList, ( gpointer )( dy ) ); + goodle_doodle_session_draw_brush_line( ds->drawing_area, ds, + ds->brush_color, ds->brush_size ); + // Set tracking variables +// ------------------------------------------------------------------------------------------------------ +gboolean goodle_doodle_session_brush_up( GtkWidget *widget, GdkEventButton *event, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + GdkPixmap *pixmap = ds->pixmap; + //g_print( "BRUSH_UP | %s\n", ds->who ); + if( ( BrushState != BRUSH_STATE_DOWN ) && ( BrushState != BRUSH_STATE_MOTION ) ) + g_print( "***Bad brush state transition %d to UP\n", BrushState ); + BrushState = BRUSH_STATE_UP; + BrushState = BRUSH_STATE_UP; + if( event->button == 1 && pixmap != NULL ) + // If the brush was never moved, express two sets of two deltas + // That's a 'point,' but not for Yahoo! + //if( ( event->x == LastX ) && ( event->y == LastY ) ) + for( index = 0; index < 2; index++ ) + drawList = g_list_append( drawList, ( gpointer )( x0 ) ); + drawList = g_list_append( drawList, ( gpointer )( y0 ) ); + char* message = goodle_doodle_session_build_draw_string( drawList ); + yahoo_doodle_command_send_draw( ds->gc, ds->who, message ); + goodle_doodle_session_set_canvas_as_icon( ds ); + // The brush stroke is finished, clear the list for another one + goodle_doodle_session_destroy_draw_list( drawList ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_draw_brush_point( GtkWidget *widget, doodle_session *ds, + int x, int y, int color, int size ) + GdkPixmap *pixmap = ds->pixmap; + //g_print( "goodle_doodle_session_draw_brush | %s\n", ds->who ); + GdkRectangle update_rect; + update_rect.x = x - size / 2; + update_rect.y = y - size / 2; + update_rect.width = size; + update_rect.height = size; + // Interpret and convert color + GdkGC *gfx_con = gdk_gc_new( pixmap ); + goodle_rgb24_to_rgb48( color, &col ); + gdk_gc_set_rgb_fg_color( gfx_con, &col ); + //gdk_gc_set_rgb_bg_color( gfx_con, &col ); + if( size < DOODLE_BRUSH_MEDIUM ) + // Draw a rectangle/square + gdk_draw_rectangle( pixmap, + gfx_con, // color element? + update_rect.x, update_rect.y, + update_rect.width, update_rect.height ); + update_rect.x, update_rect.y, + update_rect.width, update_rect.height, + 0, FULL_CIRCLE_DEGREES ); + gtk_widget_queue_draw_area( widget, + update_rect.x, update_rect.y, + update_rect.width, update_rect.height ); + gdk_gc_unref( gfx_con ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_draw_brush_line( GtkWidget *widget, doodle_session *ds, + int x0, int y0, int x1, int y1, int color, int size ) + gboolean steep = abs( y1 - y0 ) > abs( x1 - x0 ); + temp = x0; x0 = y0; y0 = temp; + temp = x1; x1 = y1; y1 = temp; + int dx = abs( x1 - x0 ); + int dy = abs( y1 - y0 ); + goodle_doodle_session_draw_brush_point( widget, ds, y, x, color, size ); + goodle_doodle_session_draw_brush_point( widget, ds, x, y, color, size ); + error = error + derror; + if( ( error * 2 ) >= dx ) + goodle_doodle_session_draw_brush_point( widget, ds, y, x, color, size ); + goodle_doodle_session_draw_brush_point( widget, ds, x, y, color, size ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_draw_stroke( doodle_session *ds, GList *d_list ) + // Traverse through the list and draw the points and lines + n = l->data; brush_color = *n; l = l->next; + n = l->data; brush_size = *n; l = l->next; + n = l->data; x = *n; l = l->next; + n = l->data; y = *n; l = l->next; + //g_print( "Drawing: color=%d, size=%d, (%d,%d)\n", brush_color, brush_size, x, y ); + // Pray this works and pray that the list has an even number of elements + n = l->data; dx = *n; l = l->next; + n = l->data; dy = *n; l = l->next; + goodle_doodle_session_draw_brush_line( ds->drawing_area, ds, + brush_color, brush_size ); + g_print( "Counted %d deltas\n", count ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_destroy_draw_list( GList *d_list ) + // Destroy the contents of this list + g_list_free( drawList ); +// ------------------------------------------------------------------------------------------------------ +char *goodle_doodle_session_build_draw_string( GList *d_list ) + static char message[1024]; // Hope that 500 is enough + char token_string[16]; // Token string extracted from draw list + strcpy( message, "\"" ); + // Pray this works and pray that the list has an even number of elements + sprintf( token_string, "%d,", *n ); + // This check prevents overflow + if( ( strlen( message ) + strlen( token_string ) ) < 1024 ) + strcat( message, token_string ); + message[strlen( message ) - 1] = '\"'; + //message[strlen( message )] = 0; + g_print( "Draw Message: %s\n", message ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_clear_canvas( doodle_session *ds ) + GdkPixmap *pixmap = ds->pixmap; + GtkWidget *drawing_area = ds->drawing_area; + gdk_draw_rectangle( pixmap, + drawing_area->style->white_gc, + drawing_area->allocation.width, drawing_area->allocation.height ); + gtk_widget_queue_draw_area( drawing_area, + drawing_area->allocation.width, drawing_area->allocation.height ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_button_clear_press( GtkWidget *widget, gpointer data ) + doodle_session *ds = ( doodle_session* )( data ); + yahoo_doodle_command_send_clear( ds->gc, ds->who ); + goodle_doodle_session_clear_canvas( ds ); + goodle_doodle_session_set_canvas_as_icon( ds ); +// ------------------------------------------------------------------------------------------------------ +void goodle_doodle_session_set_canvas_as_icon( doodle_session *ds ) + // Makes an icon from the doodle image + pixbuf = gdk_pixbuf_get_from_drawable( NULL, + ( GdkDrawable* )( ds->pixmap ), + gdk_drawable_get_colormap( ds->pixmap ), + DOODLE_CANVAS_WIDTH, DOODLE_CANVAS_HEIGHT ); + gtk_window_set_icon( ( GtkWindow* )( ds->window ), pixbuf ); +// ------------------------------------------------------------------------------------------------------ +void goodle_rgb24_to_rgb48( int color_rgb, GdkColor *color ) + color->red = ( color_rgb >> 8 ) | 0xFF; + color->green = ( color_rgb & 0xFF00 ) | 0xFF; + color->blue = ( ( color_rgb & 0xFF ) << 8 ) | 0xFF; +// ------------------------------------------------------------------------------------------------------ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo_doodle.h Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,162 @@
+ * @file yahoo_doodle.h The Yahoo! protocol plugin Doodle IMVironment object + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 +// DEFINES ============================================================================================= +// Doodle communication commands +#define DOODLE_CMD_REQUEST 0 +#define DOODLE_CMD_READY 1 +#define DOODLE_CMD_CLEAR 2 +#define DOODLE_CMD_DRAW 3 +#define DOODLE_CMD_EXTRA 4 +#define DOODLE_CMD_CONFIRM 5 +// Doodle communication command for shutting down (also 0) +#define DOODLE_CMD_SHUTDOWN DOODLE_CMD_REQUEST +#define DOODLE_EXTRA_NONE "\"1\"" +#define DOODLE_EXTRA_TICTACTOE "\"3\"" +#define DOODLE_EXTRA_DOTS "\"2\"" +// Doodle session states +#define DOODLE_STATE_REQUESTING 0 +#define DOODLE_STATE_REQUESTED 1 +#define DOODLE_STATE_ESTABLISHED 2 +// Doodle canvas dimensions +#define DOODLE_CANVAS_WIDTH 368 +#define DOODLE_CANVAS_HEIGHT 256 +// Doodle color codes (most likely RGB) +#define DOODLE_COLOR_RED 13369344 +#define DOODLE_COLOR_ORANGE 16737792 +#define DOODLE_COLOR_YELLOW 15658496 +#define DOODLE_COLOR_GREEN 52224 +#define DOODLE_COLOR_CYAN 52428 +#define DOODLE_COLOR_BLUE 204 +#define DOODLE_COLOR_VIOLET 5381277 +#define DOODLE_COLOR_PURPLE 13369548 +#define DOODLE_COLOR_TAN 12093547 +#define DOODLE_COLOR_BROWN 5256485 +#define DOODLE_COLOR_BLACK 0 +#define DOODLE_COLOR_GREY 11184810 +#define DOODLE_COLOR_WHITE 16777215 +#define PALETTE_NUM_OF_COLORS 12 +// Doodle brush sizes (most likely variable) +#define DOODLE_BRUSH_SMALL 2 +#define DOODLE_BRUSH_MEDIUM 5 +#define DOODLE_BRUSH_LARGE 10 +#define DOODLE_MAX_BRUSH_MOTIONS 100 +#define FULL_CIRCLE_DEGREES 23040 +#define BRUSH_STATE_UP 0 +#define BRUSH_STATE_DOWN 1 +#define BRUSH_STATE_MOTION 2 +// DATATYPES =========================================================================================== +typedef struct _doodle_session + int state; // State of Doodle session + GaimConnection *gc; // Connection associated with this session + char who[32]; // Name of the remote user + int brush_size; // Size of drawing brush + int brush_color; // Color of drawing brush + //GtkWidget *window; // Window for the Doodle session + //GtkWidget *drawing_area; // Drawing area + //GdkPixmap *pixmap; // Memory for drawing area +// PROTOTYPES ========================================================================================== +void init_plugin( GaimPlugin *plugin ); +gboolean goodle_load( GaimPlugin *plugin ); +gboolean goodle_unload( GaimPlugin *plugin ); +void goodle_destroy( GaimPlugin *plugin ); // void or gboolean? +GtkWidget *get_config_frame( GaimPlugin *plugin ); +void goodle_con_signed_on( GaimConnection *gc, gpointer data ); +void goodle_con_signed_off( GaimConnection *gc, gpointer data ); +void goodle_conv_created( GaimConversation *conv, gpointer data ); +void goodle_conv_destroyed( GaimConversation *conv, gpointer data ); +void goodle_button_press( GtkButton *button, gpointer data ); +void yahoo_doodle_process( GaimConnection *gc, char *me, char *from, char *command, char *message ); +void yahoo_doodle_command_got_request( GaimConnection *gc, char *from ); +void yahoo_doodle_command_got_ready( GaimConnection *gc, char *from ); +void yahoo_doodle_command_got_draw( GaimConnection *gc, char *from, char *message ); +void yahoo_doodle_command_got_clear( GaimConnection *gc, char *from ); +void yahoo_doodle_command_got_extra( GaimConnection *gc, char *from, char *message ); +void yahoo_doodle_command_got_confirm( GaimConnection *gc, char *from ); +void yahoo_doodle_command_got_shutdown( GaimConnection *gc, char *from ); +int yahoo_doodle_command_send_request( GaimConnection *gc, char *to ); +int yahoo_doodle_command_send_ready( GaimConnection *gc, char *to ); +void yahoo_doodle_command_send_draw( GaimConnection *gc, char *to, char *message ); +void yahoo_doodle_command_send_clear( GaimConnection *gc, char *to ); +void yahoo_doodle_command_send_extra( GaimConnection *gc, char *to, char *message ); +void yahoo_doodle_command_send_confirm( GaimConnection *gc, char *to ); +void yahoo_doodle_command_send_shutdown( GaimConnection *gc, char *to ); +doodle_session *yahoo_doodle_session_get( GList *ds_list, char *me, char *who ); +//void goodle_doodle_session_start( doodle_session *ds ); +//void goodle_doodle_session_end( GtkWidget *widget, gpointer data ); +gboolean goodle_doodle_session_configure_event( GtkWidget *widget, GdkEventConfigure *event, gpointer data ); +gboolean goodle_doodle_session_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer data ); +gboolean goodle_doodle_session_brush_down( GtkWidget *widget, GdkEventButton *event, gpointer data ); +gboolean goodle_doodle_session_brush_motion( GtkWidget *widget, GdkEventMotion *event, gpointer data ); +gboolean goodle_doodle_session_brush_up( GtkWidget *widget, GdkEventButton *event, gpointer data ); +void goodle_doodle_session_draw_brush_point( GtkWidget *widget, doodle_session *ds, + int x, int y, int color, int size ); +void goodle_doodle_session_draw_brush_line( GtkWidget *widget, doodle_session *ds, + int x0, int y0, int x1, int y1, int color, int size ); +void goodle_doodle_draw_stroke( doodle_session *ds, GList *d_list ); +void goodle_doodle_session_destroy_draw_list( GList *d_list ); +char *goodle_doodle_session_build_draw_string( GList *d_list ); +void goodle_doodle_session_clear_canvas( doodle_session *ds ); +void goodle_doodle_session_button_clear_press( GtkWidget *widget, gpointer data ); +void goodle_doodle_session_set_canvas_as_icon( doodle_session *ds ); +void goodle_rgb24_to_rgb48( int color_rgb, GdkColor *color ); +#endif // YAHOO_DOODLE_H --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo_filexfer.c Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,553 @@
+ * @file yahoo_filexfer.c Yahoo Filetransfer + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 "yahoo_packet.h" +#include "yahoo_filexfer.h" +#include "yahoo_doodle.h" +struct yahoo_xfer_data { +static void yahoo_xfer_data_free(struct yahoo_xfer_data *xd) +static void yahoo_receivefile_connected(gpointer data, gint source, GaimInputCondition condition) + struct yahoo_xfer_data *xd; + gaim_debug(GAIM_DEBUG_INFO, "yahoo", + "AAA - in yahoo_receivefile_connected\n"); + if (!(xd = xfer->data)) + gaim_xfer_error(GAIM_XFER_RECEIVE, gaim_xfer_get_account(xfer), + xfer->who, _("Unable to connect.")); + gaim_xfer_cancel_remote(xfer); + gaim_xfer_start(xfer, source, NULL, 0); + buf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", + write(xfer->fd, buf, strlen(buf)); +static void yahoo_sendfile_connected(gpointer data, gint source, GaimInputCondition condition) + struct yahoo_xfer_data *xd; + struct yahoo_packet *pkt; + gchar *size, *post, *buf; + int content_length, port; + char *filename, *encoded_filename; + gaim_debug(GAIM_DEBUG_INFO, "yahoo", + "AAA - in yahoo_sendfile_connected\n"); + if (!(xd = xfer->data)) + account = gaim_connection_get_account(gc); + gaim_xfer_error(GAIM_XFER_RECEIVE, gaim_xfer_get_account(xfer), + xfer->who, _("Unable to connect.")); + gaim_xfer_cancel_remote(xfer); + gaim_xfer_start(xfer, source, NULL, 0); + pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANSFER, YAHOO_STATUS_AVAILABLE, yd->session_id); + size = g_strdup_printf("%" G_GSIZE_FORMAT, gaim_xfer_get_size(xfer)); + filename = g_path_get_basename(gaim_xfer_get_local_filename(xfer)); + encoded_filename = yahoo_string_encode(gc, filename, NULL); + yahoo_packet_hash(pkt, "sssss", 0, gaim_connection_get_display_name(gc), + 5, xfer->who, 14, "", 27, encoded_filename, 28, size); + content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt); + buf = g_strdup_printf("Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + host = gaim_account_get_string(account, "xfer_host", YAHOO_XFER_HOST); + port = gaim_account_get_int(account, "xfer_port", YAHOO_XFER_PORT); + post = g_strdup_printf("POST http://%s:%d/notifyft HTTP/1.0\r\n" + "Content-length: %" G_GSIZE_FORMAT "\r\n" + host, port, content_length + 4 + gaim_xfer_get_size(xfer), host, port, buf); + write(xfer->fd, post, strlen(post)); + yahoo_packet_send_special(pkt, xfer->fd, 8); + yahoo_packet_free(pkt); + write(xfer->fd, "29\xc0\x80", 4); + g_free(encoded_filename); +static void yahoo_xfer_init(GaimXfer *xfer) + struct yahoo_xfer_data *xfer_data; + xfer_data = xfer->data; + account = gaim_connection_get_account(gc); + if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { + if (gaim_proxy_connect(account, gaim_account_get_string(account, "xferjp_host", YAHOOJP_XFER_HOST), + gaim_account_get_int(account, "xfer_port", YAHOO_XFER_PORT), + yahoo_sendfile_connected, xfer) == -1) + gaim_notify_error(gc, NULL, _("File Transfer Failed"), + _("Unable to establish file descriptor.")); + gaim_xfer_cancel_remote(xfer); + if (gaim_proxy_connect(account, gaim_account_get_string(account, "xfer_host", YAHOO_XFER_HOST), + gaim_account_get_int(account, "xfer_port", YAHOO_XFER_PORT), + yahoo_sendfile_connected, xfer) == -1) + gaim_notify_error(gc, NULL, _("File Transfer Failed"), + _("Unable to establish file descriptor.")); + gaim_xfer_cancel_remote(xfer); + xfer->fd = gaim_proxy_connect(account, xfer_data->host, xfer_data->port, + yahoo_receivefile_connected, xfer); + gaim_notify_error(gc, NULL, _("File Transfer Failed"), + _("Unable to establish file descriptor.")); + gaim_xfer_cancel_remote(xfer); +static void yahoo_xfer_start(GaimXfer *xfer) + /* We don't need to do anything here, do we? */ +static void yahoo_xfer_end(GaimXfer *xfer) + struct yahoo_xfer_data *xfer_data; + account = gaim_xfer_get_account(xfer); + xfer_data = xfer->data; + yahoo_xfer_data_free(xfer_data); +guint calculate_length(const gchar *l, size_t len) + for (i = 0; i < len; i++) { + if (!g_ascii_isdigit(l[i])) + return strtol(l + i, NULL, 10); +ssize_t yahoo_xfer_read(char **buffer, GaimXfer *xfer) + struct yahoo_xfer_data *xd = xfer->data; + if (gaim_xfer_get_type(xfer) != GAIM_XFER_RECEIVE) { + len = read(xfer->fd, buf, sizeof(buf)); + if ((gaim_xfer_get_size(xfer) > 0) && + (gaim_xfer_get_bytes_sent(xfer) >= gaim_xfer_get_size(xfer))) { + gaim_xfer_set_completed(xfer, TRUE); + xd->rxqueue = g_realloc(xd->rxqueue, len + xd->rxlen); + memcpy(xd->rxqueue + xd->rxlen, buf, len); + length = g_strstr_len(xd->rxqueue, len, "Content-length:"); + /* some proxies re-write this header, changing the capitalization :( + * technically that's allowed since headers are case-insensitive + * [RFC 2616, section 4.2] */ + length = g_strstr_len(xd->rxqueue, len, "Content-Length:"); + end = g_strstr_len(length, length - xd->rxqueue, "\r\n"); + if ((filelen = calculate_length(length, len - (length - xd->rxqueue)))) + gaim_xfer_set_size(xfer, filelen); + start = g_strstr_len(xd->rxqueue, len, "\r\n\r\n"); + if (!start || start > (xd->rxqueue + len)) + len -= (start - xd->rxqueue); + *buffer = g_malloc(len); + memcpy(*buffer, start, len); + *buffer = g_malloc(len); + memcpy(*buffer, buf, len); +ssize_t yahoo_xfer_write(const char *buffer, size_t size, GaimXfer *xfer) + struct yahoo_xfer_data *xd = xfer->data; + if (gaim_xfer_get_type(xfer) != GAIM_XFER_SEND) { + len = write(xfer->fd, buffer, size); + if (gaim_xfer_get_bytes_sent(xfer) >= gaim_xfer_get_size(xfer)) + gaim_xfer_set_completed(xfer, TRUE); + if ((errno != EAGAIN) && (errno != EINTR)) + if ((gaim_xfer_get_bytes_sent(xfer) + len) >= gaim_xfer_get_size(xfer)) + gaim_xfer_set_completed(xfer, TRUE); +static void yahoo_xfer_cancel_send(GaimXfer *xfer) + struct yahoo_xfer_data *xfer_data; + xfer_data = xfer->data; + account = gaim_xfer_get_account(xfer); + yahoo_xfer_data_free(xfer_data); +static void yahoo_xfer_cancel_recv(GaimXfer *xfer) + struct yahoo_xfer_data *xfer_data; + account = gaim_xfer_get_account(xfer); + xfer_data = xfer->data; + yahoo_xfer_data_free(xfer_data); +void yahoo_process_p2pfilexfer( GaimConnection *gc, struct yahoo_packet *pkt ) + // Get all the necessary values from this new packet + struct yahoo_pair *pair = l->data; + if( pair->key == 5 ) // Get who the packet is for + if( pair->key == 4 ) // Get who the packet is from + if( pair->key == 49 ) // Get the type of service + if( pair->key == 14 ) // Get the 'message' of the packet + if( pair->key == 13 ) // Get the command associated with this packet + if( pair->key == 63 ) // IMVironment name and version + if( pair->key == 64 ) // Not sure, but it does vary with initialization of Doodle + unknown = pair->value; // So, I'll keep it (for a little while atleast) + // If this packet is an IMVIRONMENT, handle it accordingly + if( !strcmp( service, "IMVIRONMENT" ) ) + // Check for a Doodle packet and handle it accordingly + if( !strcmp( imv, "doodle;11" ) ) + yahoo_doodle_process( gc, me, from, command, message ); + // If an IMVIRONMENT packet comes without a specific imviroment name + if( !strcmp( imv, "" ) ) + // It is unfortunately time to close all IMVironments with remote client + yahoo_doodle_command_got_shutdown( gc, from ); +void yahoo_process_filetransfer(GaimConnection *gc, struct yahoo_packet *pkt) + struct yahoo_xfer_data *xfer_data; + unsigned long filesize = 0L; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + expires = strtol(pair->value, NULL, 10); + filename = pair->value; + filesize = atol(pair->value); + if (pkt->service == YAHOO_SERVICE_P2PFILEXFER) { + if (service && (strcmp("FILEXFER", service) != 0)) { + gaim_debug_misc("yahoo", "unhandled service 0x%02x", pkt->service); + tmp = strchr(msg, '\006'); + /* Setup the Yahoo-specific file transfer data */ + xfer_data = g_new0(struct yahoo_xfer_data, 1); + if (!gaim_url_parse(url, &(xfer_data->host), &(xfer_data->port), &(xfer_data->path), NULL, NULL)) { + gaim_debug_misc("yahoo_filexfer", "Host is %s, port is %d, path is %s, and the full url was %s.\n", + xfer_data->host, xfer_data->port, xfer_data->path, url); + /* Build the file transfer handle. */ + xfer = gaim_xfer_new(gc->account, GAIM_XFER_RECEIVE, from); + xfer->data = xfer_data; + /* Set the info about the incoming file. */ + char *utf8_filename = yahoo_string_decode(gc, filename, TRUE); + gaim_xfer_set_filename(xfer, utf8_filename); + start = g_strrstr(xfer_data->path, "/"); + end = g_strrstr(xfer_data->path, "?"); + if (start && *start && end) { + filename = g_strndup(start, end - start); + utf8_filename = yahoo_string_decode(gc, filename, TRUE); + gaim_xfer_set_filename(xfer, utf8_filename); + gaim_xfer_set_size(xfer, filesize); + /* Setup our I/O op functions */ + gaim_xfer_set_init_fnc(xfer, yahoo_xfer_init); + gaim_xfer_set_start_fnc(xfer, yahoo_xfer_start); + gaim_xfer_set_end_fnc(xfer, yahoo_xfer_end); + gaim_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); + gaim_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); + gaim_xfer_set_read_fnc(xfer, yahoo_xfer_read); + gaim_xfer_set_write_fnc(xfer, yahoo_xfer_write); + /* Now perform the request */ + gaim_xfer_request(xfer); +void yahoo_send_file(GaimConnection *gc, const char *who, const char *file) + struct yahoo_xfer_data *xfer_data; + xfer_data = g_new0(struct yahoo_xfer_data, 1); + /* Build the file transfer handle. */ + xfer = gaim_xfer_new(gc->account, GAIM_XFER_SEND, who); + xfer->data = xfer_data; + /* Setup our I/O op functions */ + gaim_xfer_set_init_fnc(xfer, yahoo_xfer_init); + gaim_xfer_set_start_fnc(xfer, yahoo_xfer_start); + gaim_xfer_set_end_fnc(xfer, yahoo_xfer_end); + gaim_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); + gaim_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); + gaim_xfer_set_read_fnc(xfer, yahoo_xfer_read); + gaim_xfer_set_write_fnc(xfer, yahoo_xfer_write); + /* Now perform the request */ + gaim_xfer_request_accepted(xfer, file); + gaim_xfer_request(xfer); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo_filexfer.h Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,40 @@
+ * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 + * Process ymsg events, particular IMViroments like Doodle +void yahoo_process_p2pfilexfer( GaimConnection *gc, struct yahoo_packet *pkt ); + * Process ymsg file receive invites. +void yahoo_process_filetransfer(GaimConnection *gc, struct yahoo_packet *pkt); + * @param gc The GaimConnection handle. + * @param who Who are we sending it to? + * @param file What file? If NULL, user will choose after this call. +void yahoo_send_file(GaimConnection *gc, const char *who, const char *file); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/whiteboard.c Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,48 @@
+ * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 +// INCLUDES ============================================================================================= +// DATATYPES ============================================================================================ +//static GaimWhiteboardOps *ui_ops = NULL; +// FUNCTIONS ============================================================================================ +void gaim_whiteboard_draw_line( GaimWhiteBoard *board, int x1, int y1, int x2, int y2 ) + if( ui_ops && ui_ops->draw_line ) + ui_ops->draw_line( board, x1, y1, x2, y2 ); +// ------------------------------------------------------------------------------------------------------ +void gaim_whiteboard_set_ui_ops( GaimWhiteboardOps *ops ) +// ------------------------------------------------------------------------------------------------------ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/whiteboard.h Wed Jul 13 22:02:56 2005 -0400
@@ -0,0 +1,51 @@
+ * @file whiteboard.h The GaimWhiteboard core object + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 +// DEFINES ============================================================================================= +#ifndef _GAIM_WHITEBOARD_H_ +#define _GAIM_WHITEBOARD_H_ +// INCLUDES ============================================================================================ +// DATATYPES =========================================================================================== +typedef struct _GaimWhiteboard + int dummy; // remove this when more apporiate stuff is added +typedef struct _GaimWhiteBoardOps + void ( *draw_line )( GaimWhiteboard *board, int x1, int y1, int x2, int y2 ); +// PROTOTYPES ========================================================================================== +void gaim_whiteboard_draw_line( GaimWhiteboard *board, int x1, int y1, int x2, int y2 ); +void gaim_whiteboard_set_ops( GaimWhiteboardOps *ops ); +#endif // _GAIM_WHITEBOARD_H_ Binary file src/whiteboard.o has changed