changeset 11475:7fab28c991f3

[gaim-migrate @ 13717] merging gaim-doodle/whiteboard/whatever you like to call it. Needs some cleanups and doxygen comments, but has basic functionality committer: Tailor Script <tailor@pidgin.im>
author Gary Kramlich <grim@reaperworld.com>
date Fri, 09 Sep 2005 04:40:21 +0000
parents 7e9635b73ed6
children 5d3f8d9e8f92
files src/Makefile.am src/gtkmain.c src/gtkwhiteboard.c src/gtkwhiteboard.h src/protocols/yahoo/Makefile.am src/protocols/yahoo/yahoo.c src/protocols/yahoo/yahoo_doodle.c src/protocols/yahoo/yahoo_doodle.h src/protocols/yahoo/yahoo_filexfer.c src/protocols/yahoo/yahoo_filexfer.h src/prpl.h src/whiteboard.c src/whiteboard.h
diffstat 13 files changed, 2195 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile.am	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/Makefile.am	Fri Sep 09 04:40:21 2005 +0000
@@ -101,7 +101,8 @@
 	upnp.c \
 	util.c \
 	value.c \
-	xmlnode.c
+	xmlnode.c \
+	whiteboard.c
 
 gaim_coreheaders = \
 	account.h \
@@ -146,7 +147,8 @@
 	util.h \
 	value.h \
 	version.h \
-	xmlnode.h
+	xmlnode.h \
+	whiteboard.h
 
 
 
@@ -271,7 +273,8 @@
 	gtkthemes.c \
 	gtkutils.c \
 	idle.c \
-	session.c
+	session.c \
+	gtkwhiteboard.c
 
 gaim_headers = \
 	$(dbus_headers) \
@@ -311,7 +314,8 @@
 	gtkstatusbox.h \
 	gtkstock.h \
 	gtkutils.h \
-	internal.h
+	internal.h \
+	gtkwhiteboard.h
 
 gaimincludedir=$(includedir)/gaim
 gaiminclude_HEADERS = \
--- a/src/gtkmain.c	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/gtkmain.c	Fri Sep 09 04:40:21 2005 +0000
@@ -38,6 +38,7 @@
 #include "sound.h"
 #include "status.h"
 #include "util.h"
+#include "whiteboard.h"
 
 #include "gtkaccount.h"
 #include "gtkblist.h"
@@ -58,6 +59,7 @@
 #include "gtksound.h"
 #include "gtkutils.h"
 #include "gtkstock.h"
+#include "gtkwhiteboard.h"
 
 #if HAVE_SIGNAL_H
 # include <signal.h>
@@ -256,6 +258,7 @@
 	gaim_request_set_ui_ops(gaim_gtk_request_get_ui_ops());
 	gaim_sound_set_ui_ops(gaim_gtk_sound_get_ui_ops());
 	gaim_connections_set_ui_ops(gaim_gtk_connections_get_ui_ops());
+	gaim_whiteboard_set_ui_ops(gaim_gtk_whiteboard_get_ui_ops());
 
 	gaim_gtk_stock_init();
 	gaim_gtk_prefs_init();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkwhiteboard.c	Fri Sep 09 04:40:21 2005 +0000
@@ -0,0 +1,818 @@
+/*
+ * gaim
+ *
+ * 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
+ * source distribution.
+ *
+ * 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 <stdlib.h>
+
+#include "blist.h"
+
+#include "gtkwhiteboard.h"
+
+// GLOBALS =============================================================================================
+
+//GList				*buttonList		= NULL;
+
+//GdkColor			DefaultColor[PALETTE_NUM_COLORS];
+
+static gboolean			LocalShutdownRequest;
+
+static int			LastX;			// Tracks last position of the mouse when drawing
+static int			LastY;
+static int			MotionCount;		// Tracks how many brush motions made
+static int			BrushState		= BRUSH_STATE_UP;
+
+static GaimWhiteboardUiOps 	ui_ops			=
+{
+	gaim_gtk_whiteboard_create,
+	gaim_gtk_whiteboard_destroy,
+	gaim_gtk_whiteboard_set_dimensions,
+	gaim_gtk_whiteboard_draw_brush_point,
+	gaim_gtk_whiteboard_draw_brush_line,
+	gaim_gtk_whiteboard_clear
+};
+
+// FUNCTIONS ============================================================================================
+
+GaimWhiteboardUiOps *gaim_gtk_whiteboard_get_ui_ops( void )
+{
+	return( &ui_ops );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_create( GaimWhiteboard *wb )
+{
+	//g_print( "gaim_gtk_whiteboard_create()\n" );
+	
+	int i;
+	
+	GtkWidget *window;
+	GtkWidget *drawing_area;
+	
+	GtkWidget *hbox_palette;
+	GtkWidget *vbox_palette_above_canvas_and_controls;
+	GtkWidget *hbox_canvas_and_controls;
+	GtkWidget *vbox_controls;
+	
+	//	--------------------------
+	//	|[][][][palette[][][][][]|
+	//	|------------------------|
+	//	|       canvas     | con |
+	//	|                  | trol|
+	//	|                  | s   |
+	//	|                  |     |
+	//	|                  |     |
+	//	--------------------------
+	
+	GtkWidget *clear_button;
+	GtkWidget *save_button;
+	
+	GtkWidget *palette_color_box[PALETTE_NUM_COLORS];
+	GdkPixbuf *palette_color_area[PALETTE_NUM_COLORS];
+	
+	GaimGtkWhiteboard *gtkwb = g_new0( GaimGtkWhiteboard, 1 );
+	gtkwb->wb = wb;
+	wb->ui_data = gtkwb;
+	
+	// Get dimensions (default?) for the whiteboard canvas
+	if( wb->prpl_ops && wb->prpl_ops->get_dimensions )
+		wb->prpl_ops->get_dimensions( wb, &gtkwb->width, &gtkwb->height );
+	
+	window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
+	gtkwb->window = window;
+	gtk_widget_set_name( window, wb->who );
+	
+	// Try and set window title as the name of the buddy, else just use their username
+	const char *window_title = gaim_contact_get_alias( gaim_buddy_get_contact( gaim_find_buddy( wb->account, wb->who ) ) );
+	if( window_title )
+		gtk_window_set_title( ( GtkWindow* )( window ), window_title );
+	else
+		gtk_window_set_title( ( GtkWindow* )( window ), wb->who );
+	
+	gtk_window_set_resizable( ( GtkWindow* )( window ), FALSE );
+	
+	g_signal_connect( G_OBJECT( window ), "destroy",
+			  G_CALLBACK( gaim_gtk_whiteboard_exit ), ( gpointer )( gtkwb ) );
+	
+	// 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_COLORS; i++ )
+	{
+		//palette_color_area[i] = 
+		
+		palette_color_box[i] = gtk_image_new_from_pixbuf( NULL );
+		gtk_widget_set_size_request( palette_color_box[i], gtkwb->width / PALETTE_NUM_COLORS ,32 );
+		gtk_container_add( GTK_CONTAINER( hbox_palette ), palette_color_box[i] );
+		
+		
+		
+		gtk_widget_show( palette_color_box[i] );
+	}
+	
+	// Create the drawing area
+	drawing_area = gtk_drawing_area_new();
+	gtkwb->drawing_area = drawing_area;
+	gtk_widget_set_size_request( GTK_WIDGET( drawing_area ), gtkwb->width, gtkwb->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( gaim_gtk_whiteboard_expose_event ), ( gpointer )( gtkwb ) );
+	
+	g_signal_connect( G_OBJECT( drawing_area ), "configure_event",
+			  G_CALLBACK( gaim_gtk_whiteboard_configure_event ), ( gpointer )( gtkwb ) );
+	
+	// Event signals
+	g_signal_connect( G_OBJECT( drawing_area ), "button_press_event",
+			  G_CALLBACK( gaim_gtk_whiteboard_brush_down ), ( gpointer )( gtkwb ) );
+	
+	g_signal_connect( G_OBJECT( drawing_area ), "motion_notify_event",
+			  G_CALLBACK( gaim_gtk_whiteboard_brush_motion ), ( gpointer )( gtkwb ) );
+	
+	g_signal_connect( G_OBJECT( drawing_area ), "button_release_event",
+			  G_CALLBACK( gaim_gtk_whiteboard_brush_up ), ( gpointer )( gtkwb ) );
+	
+	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 );
+	
+	// Add a clear button
+	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( gaim_gtk_whiteboard_button_clear_press ), ( gpointer )( gtkwb ) );
+	
+	// Add a save button
+	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 );
+	
+	g_signal_connect( G_OBJECT( save_button ), "clicked",
+			  G_CALLBACK( gaim_gtk_whiteboard_button_save_press ), ( gpointer )( gtkwb ) );
+	
+	// Make all this (window) visible
+	gtk_widget_show( window );
+	
+	gaim_gtk_whiteboard_set_canvas_as_icon( gtkwb );
+	
+	// TODO Specific protocol/whiteboard assignment here? Needs a UI Op?
+	// Set default brush size and color
+	//ds->brush_size	= DOODLE_BRUSH_MEDIUM;
+	//ds->brush_color	= 0;	// black
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_destroy( GaimWhiteboard *wb )
+{
+	//g_print( "gaim_gtk_whiteboard_destroy()\n" );
+	
+	GaimGtkWhiteboard *gtkwb = wb->ui_data;
+	
+	// TODO Ask if user wants to save picture before the session is closed
+	
+	// Clear graphical memory
+	if( gtkwb->pixmap )
+	{
+		//g_print( "---gtkwb->pixmap = %p\n", gtkwb->pixmap );
+		g_object_unref( gtkwb->pixmap );
+		gtkwb->pixmap = NULL;
+	}
+	
+	if( gtkwb->window )
+	{
+		//g_print( "---gtkwb->window = %p\n", gtkwb->window );
+		gtk_widget_destroy( gtkwb->window );
+		gtkwb->window = NULL;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_exit( GtkWidget *widget, gpointer data )
+{
+	//g_print( "gaim_gtk_whiteboard_exit()\n" );
+	
+	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
+	GaimWhiteboard		*wb	= gtkwb->wb;
+	
+	if( gtkwb->window && gtkwb->pixmap )
+	{
+		LocalShutdownRequest = TRUE;
+		
+		//g_print( "---gtkwb->window = %p\n", gtkwb->window );
+		gaim_gtk_whiteboard_destroy( wb );
+	}
+	else
+		LocalShutdownRequest = FALSE;
+	
+	if( gtkwb )
+	{
+		//g_print( "---gtkwb = %p\n", gtkwb );
+		g_free( gtkwb );
+		
+		gtkwb = NULL;
+		wb->ui_data = NULL;
+	}
+	
+	// Destroy whiteboard core, if the local user exited the whiteboard window
+	if( wb && LocalShutdownRequest )
+	{
+		//g_print( "---wb = %p\n", wb );
+		gaim_whiteboard_destroy( wb );
+		wb = NULL;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+/*
+// Whiteboard start button on conversation window (move this code to gtkconv? and use new prpl_info member?)
+void gaim_gtkwhiteboard_button_start_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 ) );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb			= gaim_whiteboard_get( account, to );
+	
+	// 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 ) );
+	
+	yahoo_doodle_command_send_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.	
+	wb = gaim_whiteboard_create( account, to, DOODLE_STATE_REQUESTING );
+}
+*/
+// ------------------------------------------------------------------------------------------------------
+
+gboolean gaim_gtk_whiteboard_configure_event( GtkWidget *widget, GdkEventConfigure *event, gpointer data )
+{
+	//g_print( "gaim_gtk_whiteboard_configure_event | %s\n", ds->who );
+	
+	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
+	
+	GdkPixmap		*pixmap = gtkwb->pixmap;
+	
+	if( pixmap )
+		g_object_unref( pixmap );
+
+	pixmap = gdk_pixmap_new( widget->window,
+				 widget->allocation.width,
+				 widget->allocation.height,
+				 -1 );
+	
+	gtkwb->pixmap = pixmap;
+	
+	gdk_draw_rectangle( pixmap,
+			    widget->style->white_gc,
+			    TRUE,
+			    0, 0,
+			    widget->allocation.width,
+			    widget->allocation.height );
+
+	return( TRUE );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+gboolean gaim_gtk_whiteboard_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer data )
+{
+	//g_print( "gaim_gtk_whiteboard_expose_event | %s\n", ds->who );
+	
+	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
+	GdkPixmap		*pixmap	= gtkwb->pixmap;
+	
+	gdk_draw_drawable( widget->window,
+			   widget->style->fg_gc[GTK_WIDGET_STATE( widget )],
+			   pixmap,
+			   event->area.x, event->area.y,
+			   event->area.x, event->area.y,
+			   event->area.width, event->area.height );
+	
+	return( FALSE );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+gboolean gaim_gtk_whiteboard_brush_down( GtkWidget *widget, GdkEventButton *event, gpointer data )
+{	
+	GaimGtkWhiteboard	*gtkwb		= ( GaimGtkWhiteboard* )( data );
+	//g_print( "gaim_gtk_whiteboard_brush_down | %s\n", gtkwb->wb->who );
+	GdkPixmap		*pixmap		= gtkwb->pixmap;
+
+	GaimWhiteboard		*wb		= gtkwb->wb;
+	GList			*draw_list	= wb->draw_list;
+	
+	int			*x0		= NULL;
+	int			*y0		= 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;
+		
+		//return( FALSE );
+	}
+	
+	BrushState = BRUSH_STATE_DOWN;
+	
+	if( event->button == 1 && pixmap != NULL )
+	{
+		// Check if draw_list has contents; if so, clear it
+		if( draw_list )
+			draw_list = gaim_whiteboard_draw_list_destroy( draw_list );
+		
+		x0		= g_new0( int, 1 );
+		y0		= g_new0( int, 1 );
+		
+		*x0		= event->x;
+		*y0		= event->y;
+		
+		// Set tracking variables
+		LastX		= *x0;
+		LastY		= *y0;
+		
+		MotionCount	= 0;
+		
+		draw_list	= g_list_append( draw_list, ( gpointer )( x0 ) );
+		draw_list	= g_list_append( draw_list, ( gpointer )( y0 ) );
+		
+		gaim_gtk_whiteboard_draw_brush_point( gtkwb->wb,
+						      event->x, event->y,
+						      0,5 );//gtkwb->brush_color, gtkwb->brush_size ); NOTE temp const prot uiop
+	}
+	
+	wb->draw_list = draw_list;
+	
+	return( TRUE );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+gboolean gaim_gtk_whiteboard_brush_motion( GtkWidget *widget, GdkEventMotion *event, gpointer data )
+{	
+	int			x;
+	int			y;
+	int			*dx;
+	int			*dy;
+	
+	GdkModifierType		state;
+	
+	GaimGtkWhiteboard	*gtkwb		= ( GaimGtkWhiteboard* )( data );
+	//g_print( "gaim_gtk_whiteboard_brush_motion | %s\n", gtkwb->wb->who );
+	GdkPixmap		*pixmap		= gtkwb->pixmap;
+	
+	GaimWhiteboard		*wb		= gtkwb->wb;
+	GList			*draw_list	= wb->draw_list;
+
+	if( event->is_hint )
+		gdk_window_get_pointer( event->window, &x, &y, &state );
+	else
+	{
+		x	= event->x;
+		y	= event->y;
+		state	= event->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;
+			
+			return( FALSE );
+		}
+		BrushState = BRUSH_STATE_MOTION;
+		
+		dx	= g_new0( int, 1 );
+		dy	= g_new0( int, 1 );
+		
+		*dx	= x - LastX;
+		*dy	= y - LastY;
+		
+		MotionCount++;
+		
+		// NOTE 100 is a temporary constant for how many deltas/motions in a stroke (needs UI Ops?)
+		if( MotionCount == 100 )
+		{
+			draw_list = g_list_append( draw_list, ( gpointer )( dx ) );
+			draw_list = g_list_append( draw_list, ( gpointer )( dy ) );
+			
+			// Send draw list to prpl draw_list handler
+			if( gtkwb->wb->prpl_ops && gtkwb->wb->prpl_ops->send_draw_list )
+				gtkwb->wb->prpl_ops->send_draw_list( gtkwb->wb, draw_list );
+			
+			// The brush stroke is finished, clear the list for another one
+			if( draw_list )
+				draw_list = gaim_whiteboard_draw_list_destroy( draw_list );
+			
+			int *x0			= g_new0( int, 1 );
+			int *y0			= g_new0( int, 1 );
+			
+			*x0			= LastX;
+			*y0			= LastY;
+		
+			// Reset motion tracking
+			MotionCount		= 0;
+			
+			draw_list		= g_list_append( draw_list, ( gpointer )( x0 ) );
+			draw_list		= g_list_append( draw_list, ( gpointer )( y0 ) );
+
+			dx	= g_new0( int, 1 );
+			dy	= g_new0( int, 1 );
+			
+			*dx	= x - LastX;
+			*dy	= y - LastY;
+		}
+		
+		draw_list = g_list_append( draw_list, ( gpointer )( dx ) );
+		draw_list = g_list_append( draw_list, ( gpointer )( dy ) );
+		
+		gaim_gtk_whiteboard_draw_brush_line( gtkwb->wb,
+						     LastX, LastY,
+						     x, y,
+						     0, 5 );//gtkwb->brush_color, gtkwb->brush_size ); temp const proto ui ops?
+		
+		// Set tracking variables
+		LastX = x;
+		LastY = y;
+	}
+	
+	wb->draw_list = draw_list;
+ 
+	return( TRUE );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+gboolean gaim_gtk_whiteboard_brush_up( GtkWidget *widget, GdkEventButton *event, gpointer data )
+{
+	GaimGtkWhiteboard	*gtkwb		= ( GaimGtkWhiteboard* )( data );
+	//g_print( "gaim_gtk_whiteboard_brush_up | %s\n", gtkwb->wb->who );
+	GdkPixmap		*pixmap		= gtkwb->pixmap;
+	
+	GaimWhiteboard		*wb		= gtkwb->wb;
+	GList			*draw_list	= wb->draw_list;
+	
+	if( ( BrushState != BRUSH_STATE_DOWN ) && ( BrushState != BRUSH_STATE_MOTION ) )
+	{
+		g_print( "***Bad brush state transition %d to UP\n", BrushState );
+		
+		BrushState = BRUSH_STATE_UP;
+		
+		return( FALSE );
+	}
+	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 ) )
+		if( MotionCount == 0 )
+		{
+			int index;
+			
+			for( index = 0; index < 2; index++ )	// NOTE Yahoo Doodle specific!
+			{
+				int *x0		= NULL;
+				int *y0		= NULL;
+			
+				x0		= g_new0( int, 1 );
+				y0		= g_new0( int, 1 );
+			
+				draw_list	= g_list_append( draw_list, ( gpointer )( x0 ) );
+				draw_list	= g_list_append( draw_list, ( gpointer )( y0 ) );
+			}
+		}
+		//else
+		//	MotionCount = 0;
+		
+		// Send draw list to prpl draw_list handler
+		if( gtkwb->wb->prpl_ops && gtkwb->wb->prpl_ops->send_draw_list )
+			gtkwb->wb->prpl_ops->send_draw_list( gtkwb->wb, draw_list );
+		
+		gaim_gtk_whiteboard_set_canvas_as_icon( gtkwb );
+		
+		// The brush stroke is finished, clear the list for another one
+		if( draw_list )
+			draw_list = gaim_whiteboard_draw_list_destroy( draw_list );
+		
+		wb->draw_list = draw_list;
+	}
+	
+	return( TRUE );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+// void gaim_gtk_whiteboard_draw_brush_point( GtkWidget *widget, GaimGtkWhiteboard *gtkwb,
+// 					  int x, int y, int color, int size )
+void gaim_gtk_whiteboard_draw_brush_point( GaimWhiteboard *wb, int x, int y, int color, int size )
+{
+	//g_print( "goodle_doodle_session_draw_brush | %s\n", ds->who );
+	
+	GaimGtkWhiteboard	*gtkwb	= wb->ui_data;
+	GtkWidget		*widget	= gtkwb->drawing_area;
+	GdkPixmap 		*pixmap	= gtkwb->pixmap;
+			
+	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 );
+	GdkColor		col;
+	
+	gaim_gtk_whiteboard_rgb24_to_rgb48( color, &col );
+	
+	gdk_gc_set_rgb_fg_color( gfx_con, &col );
+	//gdk_gc_set_rgb_bg_color( gfx_con, &col );
+	
+	// NOTE 5 is a size constant for now... this is because of how poorly the gdk_draw_arc draws small circles
+	if( size < 5 )
+	{
+		// Draw a rectangle/square
+		gdk_draw_rectangle( pixmap,
+				    gfx_con,
+				    TRUE,
+				    update_rect.x, update_rect.y,
+				    update_rect.width, update_rect.height );
+	}
+	else
+	{
+		// Draw a circle
+		gdk_draw_arc( pixmap,
+			      gfx_con,
+			      TRUE,
+			      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 );
+}
+
+// ------------------------------------------------------------------------------------------------------
+// Uses Bresenham's algorithm (as provided by Wikipedia)
+// void gaim_gtk_whiteboard_draw_brush_line( GtkWidget *widget, GaimGtkWhiteboard *gtkwb,
+// 					 int x0, int y0, int x1, int y1, int color, int size )
+void gaim_gtk_whiteboard_draw_brush_line( GaimWhiteboard *wb, int x0, int y0, int x1, int y1, int color, int size )
+{	
+	int temp;
+	
+	int xstep;
+	int ystep;
+	
+	gboolean steep = abs( y1 - y0 ) > abs( x1 - x0 );
+	
+	if( steep )
+	{
+		temp = x0; x0 = y0; y0 = temp;
+		temp = x1; x1 = y1; y1 = temp;	
+	}
+	
+	int dx		= abs( x1 - x0 );
+	int dy		= abs( y1 - y0 );
+	
+	int error	= 0;
+	int derror	= dy;
+	
+	int x		= x0;
+	int y		= y0;
+	
+	if( x0 < x1 )
+		xstep = 1;
+	else
+		xstep = -1;
+	
+	if( y0 < y1 )
+		ystep = 1;
+	else
+		ystep = -1;
+	
+	if( steep )
+		gaim_gtk_whiteboard_draw_brush_point( wb, y, x, color, size );
+	else
+		gaim_gtk_whiteboard_draw_brush_point( wb, x, y, color, size );
+	
+	while( x != x1 )
+	{
+		x	= x + xstep;
+		error	= error + derror;
+		
+		if( ( error * 2 ) >= dx )
+		{
+			y	= y + ystep;
+			error	= error - dx;
+		}
+		
+		if( steep )
+			gaim_gtk_whiteboard_draw_brush_point( wb, y, x, color, size );
+		else
+			gaim_gtk_whiteboard_draw_brush_point( wb, x, y, color, size );
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_set_dimensions( GaimWhiteboard *wb, int width, int height )
+{
+	GaimGtkWhiteboard *gtkwb = wb->ui_data;
+	
+	gtkwb->width	= width;
+	gtkwb->height	= height;
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_clear( GaimWhiteboard *wb )
+{
+	GaimGtkWhiteboard	*gtkwb		= wb->ui_data;
+	GdkPixmap		*pixmap		= gtkwb->pixmap;
+	GtkWidget		*drawing_area	= gtkwb->drawing_area;
+	
+	gdk_draw_rectangle( pixmap,
+			    drawing_area->style->white_gc,
+			    TRUE,
+			    0, 0,
+			    drawing_area->allocation.width, drawing_area->allocation.height );
+		
+	gtk_widget_queue_draw_area( drawing_area,
+				    0, 0,
+				    drawing_area->allocation.width, drawing_area->allocation.height );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_button_clear_press( GtkWidget *widget, gpointer data )
+{
+	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
+	
+	gaim_gtk_whiteboard_clear( gtkwb->wb );
+	
+	gaim_gtk_whiteboard_set_canvas_as_icon( gtkwb );
+	
+	// Do protocol specific clearing procedures
+	if( gtkwb->wb->prpl_ops && gtkwb->wb->prpl_ops->clear )
+		gtkwb->wb->prpl_ops->clear( gtkwb->wb );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_button_save_press( GtkWidget *widget, gpointer data )
+{
+	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
+	GdkPixbuf		*pixbuf;
+	
+	GtkWidget *dialog;
+
+	dialog = gtk_file_chooser_dialog_new ("Save File",
+					      GTK_WINDOW(gtkwb->window),
+					      GTK_FILE_CHOOSER_ACTION_SAVE,
+					      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+					      NULL );
+	
+	//gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER( dialog ), (gboolean)(TRUE) );
+
+//	if( user_edited_a_new_document )
+	{
+//		gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( dialog ), default_folder_for_saving );
+		gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER( dialog ), "whiteboard.jpg" );
+	}
+//	else
+//		gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), filename_for_existing_document);
+
+	int result = gtk_dialog_run( GTK_DIALOG( dialog ) );
+
+	if( result == GTK_RESPONSE_ACCEPT )
+	{
+		char *filename;
+
+		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER( dialog ) );
+
+		gtk_widget_destroy( dialog );
+		
+		// Makes an icon from the whiteboard's canvas 'image'
+		pixbuf = gdk_pixbuf_get_from_drawable( NULL,
+						( GdkDrawable* )( gtkwb->pixmap ),
+						gdk_drawable_get_colormap( gtkwb->pixmap ),
+						0, 0,
+						0, 0,
+						gtkwb->width, gtkwb->height );
+		
+		if( gdk_pixbuf_save( pixbuf,
+		    		     filename,
+				     "jpeg",
+				     NULL,
+				     "quality",
+				     "100",
+				     NULL ) )
+			g_print( "File Saved...\n" );
+		else
+			g_print( "File not Saved... Error\n" );
+	}
+	else
+	if( result == GTK_RESPONSE_CANCEL )
+	{	
+		gtk_widget_destroy( dialog );
+		
+		g_print( "File not Saved... Canceled\n" );
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_set_canvas_as_icon( GaimGtkWhiteboard *gtkwb )
+{
+	GdkPixbuf *pixbuf;
+	
+	// Makes an icon from the whiteboard's canvas 'image'
+	pixbuf = gdk_pixbuf_get_from_drawable( NULL,
+					       ( GdkDrawable* )( gtkwb->pixmap ),
+					       gdk_drawable_get_colormap( gtkwb->pixmap ),
+					       0, 0,
+					       0, 0,
+					       gtkwb->width, gtkwb->height );
+	
+	gtk_window_set_icon( ( GtkWindow* )( gtkwb->window ), pixbuf );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_gtk_whiteboard_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/gtkwhiteboard.h	Fri Sep 09 04:40:21 2005 +0000
@@ -0,0 +1,89 @@
+/**
+ * @file gtkwhiteboard.h The GtkGaimWhiteboard frontend object
+ *
+ * gaim
+ *
+ * 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
+ * source distribution.
+ *
+ * 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
+ */
+
+#ifndef _GAIM_GTKWHITEBOARD_H_
+#define _GAIM_GTKWHITEBOARD_H_
+
+// INCLUDES ============================================================================================
+#include "gtkgaim.h"
+
+#include "whiteboard.h"
+
+// DEFINES =============================================================================================
+
+#define FULL_CIRCLE_DEGREES		23040
+
+#define BRUSH_STATE_UP			0
+#define BRUSH_STATE_DOWN		1
+#define BRUSH_STATE_MOTION		2
+
+#define PALETTE_NUM_COLORS		7
+
+// DATATYPES ===========================================================================================
+typedef struct _GaimGtkWhiteboard
+{
+	GaimWhiteboard	*wb;		// backend data for this whiteboard
+	
+	GtkWidget	*window;	// Window for the Doodle session
+	GtkWidget	*drawing_area;	// Drawing area
+	
+	GdkPixmap	*pixmap;	// Memory for drawing area
+	
+	int		width;		// Canvas width
+	int		height;		// Canvas height
+} GaimGtkWhiteboard;
+
+// PROTOTYPES ==========================================================================================
+
+GaimWhiteboardUiOps	*gaim_gtk_whiteboard_get_ui_ops( void );
+
+void			gaim_gtk_whiteboard_create( GaimWhiteboard *wb );
+void			gaim_gtk_whiteboard_destroy( GaimWhiteboard *wb );
+void			gaim_gtk_whiteboard_exit( GtkWidget *widget, gpointer data );
+
+//void			gaim_gtkwhiteboard_button_start_press( GtkButton *button, gpointer data );
+
+gboolean		gaim_gtk_whiteboard_configure_event( GtkWidget *widget, GdkEventConfigure *event, gpointer data );
+gboolean		gaim_gtk_whiteboard_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer data );
+
+gboolean		gaim_gtk_whiteboard_brush_down( GtkWidget *widget, GdkEventButton *event, gpointer data );
+gboolean		gaim_gtk_whiteboard_brush_motion( GtkWidget *widget, GdkEventMotion *event, gpointer data );
+gboolean		gaim_gtk_whiteboard_brush_up( GtkWidget *widget, GdkEventButton *event, gpointer data );
+
+void			gaim_gtk_whiteboard_draw_brush_point( GaimWhiteboard *wb,
+							      int x, int y, int color, int size );
+void			gaim_gtk_whiteboard_draw_brush_line( GaimWhiteboard *wb,
+							     int x0, int y0, int x1, int y1, int color, int size );
+
+void			gaim_gtk_whiteboard_set_dimensions( GaimWhiteboard *wb, int width, int height );
+void			gaim_gtk_whiteboard_clear( GaimWhiteboard *wb );
+
+void			gaim_gtk_whiteboard_button_clear_press( GtkWidget *widget, gpointer data );
+void			gaim_gtk_whiteboard_button_save_press( GtkWidget *widget, gpointer data );
+
+void			gaim_gtk_whiteboard_set_canvas_as_icon( GaimGtkWhiteboard *gtkwb );
+
+void			gaim_gtk_whiteboard_rgb24_to_rgb48( int color_rgb, GdkColor *color );
+
+#endif // _GAIM_GTKWHITEBOARD_H_
--- a/src/protocols/yahoo/Makefile.am	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/protocols/yahoo/Makefile.am	Fri Sep 09 04:40:21 2005 +0000
@@ -18,6 +18,8 @@
 	yahoo_friend.c \
 	yahoo_packet.h \
 	yahoo_packet.c \
+	yahoo_doodle.h \
+	yahoo_doodle.c \
 	yahoo_picture.c \
 	yahoo_picture.h \
 	yahoo_profile.c \
--- a/src/protocols/yahoo/yahoo.c	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/protocols/yahoo/yahoo.c	Fri Sep 09 04:40:21 2005 +0000
@@ -46,6 +46,7 @@
 #include "yahoo_auth.h"
 #include "yahoo_filexfer.h"
 #include "yahoo_picture.h"
+#include "yahoo_doodle.h"
 
 extern char *yahoo_crypt(const char *, const char *);
 
@@ -664,6 +665,8 @@
 	GSList *l = pkt->hash;
 	GSList *list = NULL;
 	struct _yahoo_im *im = NULL;
+	
+	char imv[16];
 
 	if (pkt->status <= 1 || pkt->status == 5) {
 		while (l) {
@@ -687,12 +690,35 @@
 				if (im)
 					im->msg = pair->value;
 			}
+			// IMV key
+			if (pair->key == 63)
+			{
+				strcpy( imv, pair->value );
+			}
 			l = l->next;
 		}
 	} else if (pkt->status == 2) {
 		gaim_notify_error(gc, NULL,
 		                  _("Your Yahoo! message did not get sent."), NULL);
 	}
+	
+	// Check for the Doodle IMV
+	if( !strcmp( imv, "doodle;11" ) )
+	{
+		g_print( "'doodle;11' found in chat packet\n" );
+		
+		GaimWhiteboard *wb = gaim_whiteboard_get_session( gc->account, im->from );
+		
+		// If a Doodle session doesn't exist between this user
+		if( wb == NULL )
+		{
+			g_print( "Creating new whiteboard for chat packet request\n" );
+			wb = gaim_whiteboard_create( gc->account, im->from, DOODLE_STATE_REQUESTED );
+			
+			yahoo_doodle_command_send_request( gc, im->from );
+			yahoo_doodle_command_send_ready( gc, im->from );
+		}
+	}
 
 	for (l = list; l; l = l->next) {
 		YahooFriend *f;
@@ -2057,6 +2083,7 @@
 		yahoo_process_stealth(gc, pkt);
 		break;
 	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);
 		break;
@@ -2964,7 +2991,14 @@
 		yahoo_packet_hash_str(pkt, 97, "1");
 	yahoo_packet_hash_str(pkt, 14, msg2);
 
-	yahoo_packet_hash_str(pkt,   63, ";0"); /* IMvironment */
+	// If this message is to a user who is also Doodling with the local user,
+	// format the chat packet with the correct IMV information (thanks Yahoo!)
+	GaimWhiteboard *wb = gaim_whiteboard_get_session(gc->account, (char*)who);
+	if (wb)
+		yahoo_packet_hash_str(pkt,   63, "doodle;11");
+	else
+		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 */
 	if (!yd->picture_url)
@@ -3508,8 +3542,23 @@
 	                  GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-yahoo", yahoogaim_cmd_buzz,
 	                  _("buzz: Buzz a contact to get their attention"), NULL);
+	
+	gaim_cmd_register("doodle", "", GAIM_CMD_P_PRPL,
+			  GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY,
+			  "prpl-yahoo", yahoo_doodle_gaim_cmd_start,
+			  _("doodle: Request user to start a Doodle session"), NULL);
 }
 
+static GaimWhiteboardPrplOps yahoo_whiteboard_prpl_ops =
+{
+	yahoo_doodle_start,
+	yahoo_doodle_end,
+	yahoo_doodle_get_dimensions,
+	NULL,
+	yahoo_doodle_send_draw_list,
+	yahoo_doodle_clear
+};
+
 static GaimPluginProtocolInfo prpl_info =
 {
 	OPT_PROTO_MAIL_CHECK | OPT_PROTO_CHAT_TOPIC,
@@ -3568,7 +3617,8 @@
 	yahoo_roomlist_cancel,
 	yahoo_roomlist_expand_category,
 	NULL, /* can_receive_file */
-	yahoo_send_file
+	yahoo_send_file,
+	&yahoo_whiteboard_prpl_ops
 };
 
 static GaimPluginInfo info =
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo_doodle.c	Fri Sep 09 04:40:21 2005 +0000
@@ -0,0 +1,728 @@
+/*
+ * gaim
+ *
+ * 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
+ * source distribution.
+ *
+ * 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 "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "cipher.h"
+#include "cmds.h"
+#include "debug.h"
+#include "notify.h"
+#include "privacy.h"
+#include "prpl.h"
+#include "proxy.h"
+#include "request.h"
+#include "server.h"
+#include "util.h"
+#include "version.h"
+
+#include "yahoo.h"
+#include "yahoo_packet.h"
+#include "yahoo_friend.h"
+#include "yahoochat.h"
+#include "ycht.h"
+#include "yahoo_auth.h"
+#include "yahoo_filexfer.h"
+#include "yahoo_picture.h"
+
+#include "whiteboard.h"
+#include "yahoo_doodle.h"
+
+// GLOBALS =============================================================================================
+
+const int DefaultColorRGB24[]	=
+{
+	DOODLE_COLOR_RED,
+	DOODLE_COLOR_ORANGE,
+	DOODLE_COLOR_YELLOW,
+	DOODLE_COLOR_GREEN,
+	DOODLE_COLOR_CYAN,
+	DOODLE_COLOR_BLUE,
+	DOODLE_COLOR_VIOLET,
+	DOODLE_COLOR_PURPLE,
+	DOODLE_COLOR_TAN,
+	DOODLE_COLOR_BROWN,
+	DOODLE_COLOR_BLACK,
+	DOODLE_COLOR_GREY,
+	DOODLE_COLOR_WHITE
+};
+
+// FUNCTIONS ============================================================================================
+
+GaimCmdRet yahoo_doodle_gaim_cmd_start( GaimConversation *conv, const char *cmd, char **args, char **error, void *data )
+{
+	if( *args && args[0] )
+		return( GAIM_CMD_RET_FAILED );
+	
+	GaimAccount		*account	= gaim_conversation_get_account( conv );
+	GaimConnection		*gc		= gaim_account_get_connection( account );
+	char			*to		= ( char* )( gaim_conversation_get_name( conv ) );
+	GaimWhiteboard		*wb		= gaim_whiteboard_get_session( account, to );
+	
+	// NOTE Functionalize this code?
+	
+	if( wb == NULL )
+	{
+		// Insert this 'session' in the list.  At this point, it's only a requested session.	
+		wb = gaim_whiteboard_create( account, to, DOODLE_STATE_REQUESTING );
+	}
+	//else
+	//	; // NOTE Perhaps some careful handling of remote assumed established sessions
+	
+	yahoo_doodle_command_send_request( gc, to );
+	yahoo_doodle_command_send_ready( gc, to );
+	
+	// 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 ) );
+	
+	return( GAIM_CMD_RET_OK );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+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 );
+
+	switch( cmd )
+	{
+		case DOODLE_CMD_REQUEST:
+		{
+			yahoo_doodle_command_got_request( gc, from );
+		} break;
+		
+		case DOODLE_CMD_READY:
+		{
+			yahoo_doodle_command_got_ready( gc, from );
+		} break;
+		
+		case DOODLE_CMD_CLEAR:
+		{
+			yahoo_doodle_command_got_clear( gc, from );
+		} break;
+		
+		case DOODLE_CMD_DRAW:
+		{
+			yahoo_doodle_command_got_draw( gc, from, message );
+		} break;
+		
+		case DOODLE_CMD_EXTRA:
+		{
+			yahoo_doodle_command_got_extra( gc, from, message );
+		} break;
+		
+		case DOODLE_CMD_CONFIRM:
+		{
+			yahoo_doodle_command_got_confirm( gc, from );
+		} break;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_got_request( GaimConnection *gc, char *from )
+{	
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got REQUEST (%s)\n", from );
+	
+	GaimAccount	*account	= gaim_connection_get_account( gc );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb		= gaim_whiteboard_get_session( account, from );
+	
+	// If a session with the remote user doesn't exist
+	if( wb == NULL )
+	{
+		// 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 );
+		*/
+		
+		wb = gaim_whiteboard_create( account, from, DOODLE_STATE_REQUESTED );
+		
+		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 )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got READY (%s)\n", from );
+	
+	GaimAccount	*account	= gaim_connection_get_account( gc );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb		= gaim_whiteboard_get_session( account, from );
+							
+	if( wb == NULL )
+		return;
+	
+	if( wb->state == DOODLE_STATE_REQUESTING )
+	{
+		gaim_whiteboard_start( wb );
+		
+		wb->state = DOODLE_STATE_ESTABLISHED;
+		
+		yahoo_doodle_command_send_confirm( gc, from );
+	}
+	
+	if( wb->state == DOODLE_STATE_ESTABLISHED )
+	{
+		// Ask whether to save picture too
+		
+		gaim_whiteboard_clear( wb );
+	}
+	
+	// NOTE Not sure about this... I am trying to handle if the remote user already
+	// thinks we're in a session with them (when their chat message contains the doodle;11 imv key)
+	if( wb->state == DOODLE_STATE_REQUESTED )
+	{
+		g_print( "Hmmmm\n" );
+		
+		//gaim_whiteboard_start( wb );
+		yahoo_doodle_command_send_request( gc, from );
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_got_draw( GaimConnection *gc, char *from, char *message )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got DRAW (%s)\n", from );
+	
+	g_print( "Draw Message:  %s\n", message );
+	
+	GaimAccount	*account	= gaim_connection_get_account( gc );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb		= gaim_whiteboard_get_session( account, from );
+	
+	if( wb == NULL )
+		return;
+	
+	// TODO Functionalize
+	// Convert drawing packet message to an integer list
+	
+	int	*token		= NULL;
+	int	length		= strlen( message );
+	char	*token_end;
+	
+	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] != '\"' ) )
+		return;
+	
+	// Truncate the quotations off of our message (why the hell did they add them anyways!?)
+	message[length - 1]	= ',';
+	message			= message + 1;
+	
+	// Traverse and extract all integers divided by commas
+	while( ( token_end = strchr( message, ',' ) ) )
+	{
+		token_end[0]	= 0;
+		
+		token		= g_new0( int, 1 );
+		
+		*token		= atoi( message );
+		
+		d_list		= g_list_append( d_list, ( gpointer )( token ) );
+		
+		message		= token_end + 1;
+	}
+	
+	yahoo_doodle_draw_stroke( wb, d_list );
+	
+	//goodle_doodle_session_set_canvas_as_icon( ds );
+	
+	// Remove that shit
+	int	*n = NULL;
+	GList	*l = d_list;
+	while( l )
+	{
+		n = l->data;
+		
+		g_free( n );
+		
+		l = l->next;
+	}
+	
+	g_list_free( d_list );
+	d_list = NULL;
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_got_clear( GaimConnection *gc, char *from )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got CLEAR (%s)\n", from );
+	
+	GaimAccount	*account	= gaim_connection_get_account( gc );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb		= gaim_whiteboard_get_session( account, from );
+	
+	if( wb == NULL )
+		return;
+	
+	if( wb->state == DOODLE_STATE_ESTABLISHED )
+	{
+		// TODO Ask user whether to save the image before clearing it
+		
+		gaim_whiteboard_clear( wb );
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_got_extra( GaimConnection *gc, char *from, char *message )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got EXTRA (%s)\n", from );
+	
+	// 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
+	
+	yahoo_doodle_command_send_extra( gc, from, DOODLE_EXTRA_NONE );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_got_confirm( GaimConnection *gc, char *from )
+{	
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got CONFIRM (%s)\n", from );
+	
+	// Get the doodle session
+	GaimAccount	*account	= gaim_connection_get_account( gc );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb		= gaim_whiteboard_get_session( account, from );
+	
+	if( wb == NULL )
+		return;
+	
+	// TODO Combine the following IF's?
+	
+	// Check if we requested a doodle session
+	if( wb->state == DOODLE_STATE_REQUESTING )
+	{	
+		wb->state = DOODLE_STATE_ESTABLISHED;
+		
+		gaim_whiteboard_start( wb );
+		
+		yahoo_doodle_command_send_confirm( gc, from );
+	}
+	
+	// Check if we accepted a request for a doodle session
+	if( wb->state == DOODLE_STATE_REQUESTED )
+	{	
+		wb->state = DOODLE_STATE_ESTABLISHED;
+		
+		gaim_whiteboard_start( wb );
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_got_shutdown( GaimConnection *gc, char *from )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Got SHUTDOWN (%s)\n", from );
+	
+	GaimAccount	*account	= gaim_connection_get_account( gc );
+	
+	// Only handle this if local client requested Doodle session (else local client would have sent one)
+	GaimWhiteboard	*wb		= gaim_whiteboard_get_session( account, 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
+	if( wb == NULL )
+		return;
+	else
+	{
+		gaim_whiteboard_destroy( wb );
+		
+		//yahoo_doodle_command_send_shutdown( gc, from );
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_request( GaimConnection *gc, char *to )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent REQUEST (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+	
+	yd = gc->proto_data;
+
+	// Make and send an acknowledge (ready) Doodle packet
+	pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+	yahoo_packet_hash_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		gaim_account_get_username( gc->account ) );
+	yahoo_packet_hash_str( pkt, 14,		"1" );
+	yahoo_packet_hash_str( pkt, 13,		"1" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		"doodle;11" );
+	yahoo_packet_hash_str( pkt, 64,		"1" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_ready( GaimConnection *gc, char *to )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent READY (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+
+	yd = gc->proto_data;
+	
+	// Make and send a request to start a Doodle session
+	pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+	yahoo_packet_hash_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		gaim_account_get_username( gc->account ) );
+	yahoo_packet_hash_str( pkt, 14,		"" );
+	yahoo_packet_hash_str( pkt, 13,		"0" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		"doodle;11" );
+	yahoo_packet_hash_str( pkt, 64,		"0" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_draw( GaimConnection *gc, char *to, char *message )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent DRAW (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+
+	yd = gc->proto_data;
+	
+	// Make and send a drawing packet
+	pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+	yahoo_packet_hash_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		gaim_account_get_username( gc->account ) );
+	yahoo_packet_hash_str( pkt, 14,		message );
+	yahoo_packet_hash_str( pkt, 13,		"3" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		"doodle;11" );
+	yahoo_packet_hash_str( pkt, 64,		"1" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_clear( GaimConnection *gc, char *to )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent CLEAR (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+
+	yd = gc->proto_data;
+	
+	// Make and send a request to clear packet
+	pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+	yahoo_packet_hash_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		gaim_account_get_username( gc->account ) );
+	yahoo_packet_hash_str( pkt, 14,		" " );
+	yahoo_packet_hash_str( pkt, 13,		"2" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		"doodle;11" );
+	yahoo_packet_hash_str( pkt, 64,		"1" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_extra( GaimConnection *gc, char *to, char *message )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent EXTRA (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+
+	yd = gc->proto_data;
+		
+	// 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_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		gaim_account_get_username( gc->account ) );
+	yahoo_packet_hash_str( pkt, 14,		message );
+	yahoo_packet_hash_str( pkt, 13,		"4" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		"doodle;11" );
+	yahoo_packet_hash_str( pkt, 64,		"1" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_confirm( GaimConnection *gc, char *to )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent CONFIRM (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+
+	yd = gc->proto_data;
+	
+	// Send ready packet (that local client accepted and is ready)
+	pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+	yahoo_packet_hash_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		( char* )( gaim_account_get_username( gc->account ) ) );
+	yahoo_packet_hash_str( pkt, 14,		"1" );
+	yahoo_packet_hash_str( pkt, 13,		"5" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		"doodle;11" );
+	yahoo_packet_hash_str( pkt, 64,		"1" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_command_send_shutdown( GaimConnection *gc, char *to )
+{
+	g_print( "-----------------------------------------------\n" );
+	g_print( "Sent SHUTDOWN (%s)\n", to );
+	
+	struct yahoo_data	*yd;
+	struct yahoo_packet	*pkt;
+
+	yd = gc->proto_data;
+	
+	// Declare that you are ending the Doodle session
+	pkt = yahoo_packet_new( YAHOO_SERVICE_P2PFILEXFER, YAHOO_STATUS_AVAILABLE, 0 );
+	yahoo_packet_hash_str( pkt, 49,		"IMVIRONMENT" );
+	yahoo_packet_hash_str( pkt, 1,		gaim_account_get_username( gc->account ) );
+	yahoo_packet_hash_str( pkt, 14,		"" );
+	yahoo_packet_hash_str( pkt, 13,		"0" );
+	yahoo_packet_hash_str( pkt, 5,		to );
+	yahoo_packet_hash_str( pkt, 63,		";0" );
+	yahoo_packet_hash_str( pkt, 64,		"0" );
+	yahoo_packet_hash_str( pkt, 1002,	"1" );
+	
+	yahoo_packet_send_and_free( pkt, yd );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_start( GaimWhiteboard *wb )
+{
+	//g_print( "yahoo_doodle_start()\n" );
+	
+	doodle_session *ds	= g_new0( doodle_session, 1 );
+
+	// Set default brush size and color
+	ds->brush_size		= DOODLE_BRUSH_MEDIUM;
+	ds->brush_color		= 0;	// black
+	
+	wb->proto_data		= ds;
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_end( GaimWhiteboard *wb )
+{
+	//g_print( "yahoo_doodle_end()\n" );
+	
+	GaimConnection *gc = gaim_account_get_connection( wb->account );
+	
+	yahoo_doodle_command_send_shutdown( gc, wb->who );
+	
+	doodle_session *ds = wb->proto_data;
+	if( ds )
+		g_free( ds );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_get_dimensions( GaimWhiteboard *wb, int *width, int *height )
+{
+	// Standard Doodle canvases are of one size:  368x256
+	*width	= DOODLE_CANVAS_WIDTH;
+	*height	= DOODLE_CANVAS_HEIGHT;
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_send_draw_list( GaimWhiteboard *wb, GList *draw_list )
+{
+	//g_print( "yahoo_doodle_send_draw_list()\n" );
+	
+	doodle_session	*ds		= wb->proto_data;
+	char 		*message	= yahoo_doodle_build_draw_string( ds, draw_list );
+	
+	if( message )
+		yahoo_doodle_command_send_draw( wb->account->gc, wb->who, message );
+
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_clear( GaimWhiteboard *wb )
+{
+	yahoo_doodle_command_send_clear( wb->account->gc, wb->who );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void yahoo_doodle_draw_stroke( GaimWhiteboard *wb, GList *draw_list )
+{
+	// Traverse through the list and draw the points and lines
+	
+	//g_print( "Drawing: color=%d, size=%d, (%d,%d)\n", brush_color, brush_size, x, y );
+	
+	GList	*l = draw_list;
+	
+	int	*n = NULL;
+	
+	int	brush_color;
+	int	brush_size;
+	int	x;
+	int	y;
+	
+	int	dx, dy;
+	
+	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;
+	
+	int count = 0;
+	
+	// Pray this works and pray that the list has an even number of elements
+	while( l )
+	{
+		count++;
+		
+		n = l->data; dx	= *n; l = l->next;
+		n = l->data; dy	= *n; l = l->next;
+		
+		gaim_whiteboard_draw_line( wb,
+					   x, y,
+					   x + dx, y + dy,
+					   brush_color, brush_size );
+		
+		x = x + dx;
+		y = y + dy;
+	}
+	
+	//g_print( "Counted %d deltas\n", count );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+char *yahoo_doodle_build_draw_string( doodle_session *ds, GList *draw_list )
+{
+	//g_print( "yahoo_doodle_build_draw_string()\n" );
+	
+	if( draw_list == NULL )
+		return( NULL );	
+	
+	GList		*l = draw_list;
+	
+	int		*n = NULL;
+	
+	static char	message[1024];		// Hope that 1024 is enough
+	char		token_string[16];	// Token string extracted from draw list
+	
+	strcpy( message, "\"" );
+	
+	sprintf( token_string, "%d,%d,", ds->brush_color, ds->brush_size );
+	strcat( message, token_string );
+	
+	// Pray this works and pray that the list has an even number of elements
+	while( l )
+	{
+		n = l->data;
+		
+		sprintf( token_string, "%d,", *n );
+		
+		// This check prevents overflow
+		if( ( strlen( message ) + strlen( token_string ) ) < 1024 )
+			strcat( message, token_string );
+		else
+			break;
+		
+		l = l->next;
+	}
+	
+	message[strlen( message ) - 1]	= '\"';
+	//message[strlen( message )]	= 0;
+	//message[511]			= 0;
+	
+	//g_print( "Draw Message:  %s\n", message );
+	
+	return( message );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo_doodle.h	Fri Sep 09 04:40:21 2005 +0000
@@ -0,0 +1,124 @@
+/**
+ * @file yahoo_doodle.h The Yahoo! protocol plugin Doodle IMVironment object
+ *
+ * gaim
+ *
+ * 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
+ * source distribution.
+ *
+ * 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
+ */
+
+#ifndef _YAHOO_DOODLE_H_
+#define _YAHOO_DOODLE_H_
+
+// INCLUDES ============================================================================================
+#include "whiteboard.h"
+#include "cmds.h"
+
+// 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
+
+// DATATYPES ===========================================================================================
+typedef struct _doodle_session
+{
+	int		brush_size;	// Size of drawing brush
+	int		brush_color;	// Color of drawing brush
+} doodle_session;
+
+// PROTOTYPES ==========================================================================================
+
+void dummy_func( void );
+
+GaimCmdRet		yahoo_doodle_gaim_cmd_start( GaimConversation *conv, const char *cmd, char **args,
+						     char **error, void *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 );
+
+void			yahoo_doodle_command_send_request( GaimConnection *gc, char *to );
+void			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 );
+
+void			yahoo_doodle_start( GaimWhiteboard *wb );
+void			yahoo_doodle_end( GaimWhiteboard *wb );
+void			yahoo_doodle_get_dimensions( GaimWhiteboard *wb, int *width, int *height );
+void			yahoo_doodle_send_draw_list( GaimWhiteboard *wb, GList *draw_list );
+void			yahoo_doodle_clear( GaimWhiteboard *wb );
+
+void			yahoo_doodle_draw_stroke( GaimWhiteboard *wb, GList *draw_list );
+char			*yahoo_doodle_build_draw_string( doodle_session *ds, GList *draw_list );
+
+
+#endif // _YAHOO_DOODLE_H_
--- a/src/protocols/yahoo/yahoo_filexfer.c	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/protocols/yahoo/yahoo_filexfer.c	Fri Sep 09 04:40:21 2005 +0000
@@ -30,6 +30,7 @@
 #include "yahoo.h"
 #include "yahoo_packet.h"
 #include "yahoo_filexfer.h"
+#include "yahoo_doodle.h"
 
 
 
@@ -351,6 +352,64 @@
 	xfer->data = NULL;
 }
 
+void yahoo_process_p2pfilexfer( GaimConnection *gc, struct yahoo_packet *pkt )
+{
+	GSList	*l		= pkt->hash;
+	
+	char	*me 		= NULL;
+	char	*from		= NULL;
+	char	*service	= NULL;
+	char	*message	= NULL;
+	char	*command	= NULL;
+	char	*imv		= NULL;
+	char	*unknown	= NULL;
+	
+	// Get all the necessary values from this new packet
+	while( l )
+	{
+		struct yahoo_pair *pair = l->data;
+		
+		if( pair->key == 5 )		// Get who the packet is for
+			me = pair->value;
+		
+		if( pair->key == 4 )		// Get who the packet is from
+			from = pair->value;
+		
+		if( pair->key == 49 )		// Get the type of service
+			service = pair->value;
+		
+		if( pair->key == 14 )		// Get the 'message' of the packet
+			message = pair->value;
+		
+		if( pair->key == 13 )		// Get the command associated with this packet
+			command = pair->value;
+		
+		if( pair->key == 63 )		// IMVironment name and version
+			imv = pair->value;
+
+		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)
+		
+		l = l->next;
+	}
+	
+	// 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, ";0" ) )
+		{
+			// 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)
 {
 	char *from = NULL;
--- a/src/protocols/yahoo/yahoo_filexfer.h	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/protocols/yahoo/yahoo_filexfer.h	Fri Sep 09 04:40:21 2005 +0000
@@ -21,6 +21,11 @@
  */
 
 /**
+ * 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);
--- a/src/prpl.h	Fri Sep 09 04:00:35 2005 +0000
+++ b/src/prpl.h	Fri Sep 09 04:40:21 2005 +0000
@@ -84,6 +84,7 @@
 #include "plugin.h"
 #include "roomlist.h"
 #include "status.h"
+#include "whiteboard.h"
 
 struct proto_chat_entry {
 	char *label;
@@ -304,6 +305,8 @@
 	/* file transfer callbacks */
 	gboolean (*can_receive_file)(GaimConnection *, const char *who);
 	void (*send_file)(GaimConnection *, const char *who, const char *filename);
+	
+	GaimWhiteboardPrplOps *whiteboard_prpl_ops;
 };
 
 #define GAIM_IS_PROTOCOL_PLUGIN(plugin) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/whiteboard.c	Fri Sep 09 04:40:21 2005 +0000
@@ -0,0 +1,216 @@
+/*
+ * gaim
+ *
+ * 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
+ * source distribution.
+ *
+ * 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 <string.h>
+
+#include "whiteboard.h"
+#include "prpl.h"
+
+// DATATYPES ============================================================================================
+
+// GLOBALS ==============================================================================================
+
+static GaimWhiteboardUiOps	*whiteboard_ui_ops	= NULL;
+//static GaimWhiteboardPrplOps	*whiteboard_prpl_ops	= NULL;
+
+static GList			*wbList			= NULL;
+
+//static gboolean		auto_accept		= TRUE;
+
+// FUNCTIONS ============================================================================================
+
+void gaim_whiteboard_set_ui_ops( GaimWhiteboardUiOps *ops )
+{
+	whiteboard_ui_ops = ops;
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_set_prpl_ops( GaimWhiteboard *wb, GaimWhiteboardPrplOps *ops )
+{
+	wb->prpl_ops = ops;
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+GaimWhiteboard *gaim_whiteboard_create( GaimAccount *account, char *who, int state )
+{
+	//g_print( "gaim_whiteboard_create()\n" );
+	
+	GaimWhiteboard *wb	= g_new0( GaimWhiteboard, 1 );
+	
+	wb->account		= account;
+	wb->state		= state;
+	wb->who			= g_strdup( who );
+	
+	GaimPluginProtocolInfo *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO( account->gc->prpl );
+	gaim_whiteboard_set_prpl_ops( wb, prpl_info->whiteboard_prpl_ops );
+	
+	// Start up protocol specifics
+	if( wb->prpl_ops && wb->prpl_ops->start )
+		wb->prpl_ops->start( wb );
+	
+	wbList			= g_list_append( wbList, ( gpointer )( wb ) );
+	
+	return( wb );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_destroy( GaimWhiteboard *wb )
+{
+	//g_print( "gaim_whiteboard_destroy()\n" );
+
+	if( wb->ui_data )
+	{
+		//g_print( "---wb->ui_data = %p\n", wb->ui_data );
+		
+		// Destroy frontend
+		if( whiteboard_ui_ops && whiteboard_ui_ops->destroy )
+			whiteboard_ui_ops->destroy( wb );
+	}
+	
+	// Do protocol specific session ending procedures
+	if( wb->prpl_ops && wb->prpl_ops->end )
+		wb->prpl_ops->end( wb );
+	
+	if( wb )
+	{
+		//g_print( "---wb = %p\n", wb );
+		
+		wb->account	= NULL;
+		wb->state	= 0;
+	
+		if( wb->who )
+			g_free( wb->who );
+			
+		wbList = g_list_remove( wbList, wb );
+		
+		g_free( wb );
+		wb = NULL;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_start( GaimWhiteboard *wb )
+{
+	//g_print( "gaim_whiteboard_start()\n" );
+	
+	// Create frontend for whiteboard
+	if( whiteboard_ui_ops && whiteboard_ui_ops->create )
+		whiteboard_ui_ops->create( wb );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+// Looks through the list of whiteboard sessions for one that is between usernames 'me' and 'who'
+// Returns a pointer to a matching whiteboard session; if none match, it returns NULL
+GaimWhiteboard *gaim_whiteboard_get_session( GaimAccount *account, char *who )
+{
+	//g_print( "gaim_whiteboard_get_session()\n" );
+	
+	GaimWhiteboard	*wb	= NULL;
+	
+	GList		*l	= wbList;
+	
+	// Look for a whiteboard session between the local user and the remote user
+	while( l )
+	{
+		wb = l->data;
+		
+		if( !strcmp( gaim_account_get_username( wb->account ), gaim_account_get_username( account ) ) &&
+			     !strcmp( wb->who, who ) )
+			return( wb );
+		
+		l = l->next;	
+	}
+	
+	return( NULL );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+GList *gaim_whiteboard_draw_list_destroy( GList *draw_list )
+{
+	//g_print( "gaim_whiteboard_draw_list_destroy()\n" );
+	
+	if( draw_list == NULL )
+		return( NULL );	
+	else
+	{
+		// Destroy the contents of this list
+		int	*n = NULL;
+		GList	*l = draw_list;
+		while( l )
+		{
+			n = l->data;
+			
+			if( n )
+				g_free( n );
+			
+			l = l->next;
+		}
+		
+		g_list_free( draw_list );
+		draw_list = NULL;
+	}
+	
+	return( draw_list );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_set_dimensions( GaimWhiteboard *wb, int width, int height )
+{
+	if( whiteboard_ui_ops && whiteboard_ui_ops->set_dimensions )
+		whiteboard_ui_ops->set_dimensions( wb, width, height );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_draw_point( GaimWhiteboard *wb, int x, int y, int color, int size )
+{
+	if( whiteboard_ui_ops && whiteboard_ui_ops->draw_point )
+		whiteboard_ui_ops->draw_point( wb, x, y, color, size );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_draw_line( GaimWhiteboard *wb, int x1, int y1, int x2, int y2, int color, int size )
+{
+	if( whiteboard_ui_ops && whiteboard_ui_ops->draw_line )
+		whiteboard_ui_ops->draw_line( wb, x1, y1, x2, y2, color, size );
+}
+
+// ------------------------------------------------------------------------------------------------------
+
+void gaim_whiteboard_clear( GaimWhiteboard *wb )
+{
+	if( whiteboard_ui_ops && whiteboard_ui_ops->clear )
+		whiteboard_ui_ops->clear( wb );
+}
+
+// ------------------------------------------------------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/whiteboard.h	Fri Sep 09 04:40:21 2005 +0000
@@ -0,0 +1,88 @@
+/**
+ * @file whiteboard.h The GaimWhiteboard core object
+ *
+ * gaim
+ *
+ * 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
+ * source distribution.
+ *
+ * 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
+ */
+
+#ifndef _GAIM_WHITEBOARD_H_
+#define _GAIM_WHITEBOARD_H_
+
+// DEFINES =============================================================================================
+
+typedef struct _GaimWhiteboardPrplOps GaimWhiteboardPrplOps;	// NOTE A nasty compiler dependency fix
+
+#include "account.h"
+
+// INCLUDES ============================================================================================
+
+// DATATYPES ===========================================================================================
+typedef struct _GaimWhiteboard
+{
+	int			state;		// State of whiteboard session
+	
+	GaimAccount		*account;	// Account associated with this session
+	char			*who;		// Name of the remote user
+	
+	void			*ui_data;	// Graphical user-interface data
+	void			*proto_data;	// Protocol specific data
+	GaimWhiteboardPrplOps	*prpl_ops;	// Protocol-plugin operations
+	
+	GList			*draw_list;	// List of drawing elements/deltas to send
+} GaimWhiteboard;
+
+typedef struct _GaimWhiteboardUiOps
+{
+	void ( *create )( GaimWhiteboard *wb );
+	void ( *destroy )( GaimWhiteboard *wb );
+	void ( *set_dimensions)( GaimWhiteboard *wb, int width, int height );
+	void ( *draw_point )( GaimWhiteboard *wb, int x, int y, int color, int size );
+	void ( *draw_line )( GaimWhiteboard *wb, int x1, int y1, int x2, int y2, int color, int size );
+	void ( *clear )( GaimWhiteboard *wb );
+} GaimWhiteboardUiOps;
+
+struct _GaimWhiteboardPrplOps
+{
+	void ( *start )( GaimWhiteboard *wb );
+	void ( *end )( GaimWhiteboard *wb );
+	void ( *get_dimensions )( GaimWhiteboard *wb, int *width, int *height );
+	void ( *set_dimensions )( GaimWhiteboard *wb, int width, int height );
+	void ( *send_draw_list )( GaimWhiteboard *wb, GList *draw_list );
+	void ( *clear )( GaimWhiteboard *wb );
+};
+
+// PROTOTYPES ==========================================================================================
+
+void		gaim_whiteboard_set_ui_ops( GaimWhiteboardUiOps *ops );
+
+GaimWhiteboard	*gaim_whiteboard_create( GaimAccount *account, char *who, int state );
+void		gaim_whiteboard_destroy( GaimWhiteboard *wb );
+void		gaim_whiteboard_start( GaimWhiteboard *wb );
+
+GaimWhiteboard	*gaim_whiteboard_get_session( GaimAccount *account, char *who );
+
+GList		*gaim_whiteboard_draw_list_destroy( GList *draw_list );
+
+void		gaim_whiteboard_set_dimensions( GaimWhiteboard *wb, int width, int height );
+void		gaim_whiteboard_draw_point( GaimWhiteboard *wb, int x, int y, int color, int size );
+void		gaim_whiteboard_draw_line( GaimWhiteboard *wb, int x1, int y1, int x2, int y2, int color, int size );
+void		gaim_whiteboard_clear( GaimWhiteboard *wb );
+
+#endif // _GAIM_WHITEBOARD_H_