1 /*
2 
3   wb.c
4 
5   Author: Pekka Riikonen <priikone@silcnet.org>
6 
7   Copyright (C) 2005 Pekka Riikonen
8 
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12 
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17 
18 */
19 
20 #include "silcincludes.h"
21 #include "silcclient.h"
22 #include "silcpurple.h"
23 #include "wb.h"
24 
25 /*
26   SILC Whiteboard packet:
27 
28   1 byte	command
29   2 bytes	width
30   2 bytes	height
31   4 bytes	brush color
32   2 bytes	brush size
33   n bytes	data
34 
35   Data:
36 
37   4 bytes	x
38   4 bytes	y
39 
40   Commands:
41 
42   0x01		draw
43   0x02		clear
44 
45   MIME:
46 
47   MIME-Version: 1.0
48   Content-Type: application/x-wb
49   Content-Transfer-Encoding: binary
50 
51 */
52 
53 #define SILCPURPLE_WB_MIME "MIME-Version: 1.0\r\nContent-Type: application/x-wb\r\nContent-Transfer-Encoding: binary\r\n\r\n"
54 #define SILCPURPLE_WB_HEADER strlen(SILCPURPLE_WB_MIME) + 11
55 
56 #define SILCPURPLE_WB_WIDTH 500
57 #define SILCPURPLE_WB_HEIGHT 400
58 #define SILCPURPLE_WB_WIDTH_MAX 1024
59 #define SILCPURPLE_WB_HEIGHT_MAX 1024
60 
61 /* Commands */
62 typedef enum {
63 	SILCPURPLE_WB_DRAW 	= 0x01,
64 	SILCPURPLE_WB_CLEAR	= 0x02
65 } SilcPurpleWbCommand;
66 
67 /* Brush size */
68 typedef enum {
69 	SILCPURPLE_WB_BRUSH_SMALL = 2,
70 	SILCPURPLE_WB_BRUSH_MEDIUM = 5,
71 	SILCPURPLE_WB_BRUSH_LARGE = 10
72 } SilcPurpleWbBrushSize;
73 
74 /* Brush color (XXX Purple should provide default colors) */
75 typedef enum {
76 	SILCPURPLE_WB_COLOR_BLACK		= 0,
77 	SILCPURPLE_WB_COLOR_RED		= 13369344,
78 	SILCPURPLE_WB_COLOR_GREEN		= 52224,
79 	SILCPURPLE_WB_COLOR_BLUE		= 204,
80 	SILCPURPLE_WB_COLOR_YELLOW 	= 15658496,
81 	SILCPURPLE_WB_COLOR_ORANGE	= 16737792,
82 	SILCPURPLE_WB_COLOR_CYAN		= 52428,
83 	SILCPURPLE_WB_COLOR_VIOLET	= 5381277,
84 	SILCPURPLE_WB_COLOR_PURPLE	= 13369548,
85 	SILCPURPLE_WB_COLOR_TAN		= 12093547,
86 	SILCPURPLE_WB_COLOR_BROWN		= 5256485,
87 	SILCPURPLE_WB_COLOR_GREY		= 11184810,
88 	SILCPURPLE_WB_COLOR_WHITE		= 16777215
89 } SilcPurpleWbColor;
90 
91 typedef struct {
92 	int type;		/* 0 = buddy, 1 = channel */
93 	union {
94 		SilcClientEntry client;
95 		SilcChannelEntry channel;
96 	} u;
97 	int width;
98 	int height;
99 	int brush_size;
100 	int brush_color;
101 } *SilcPurpleWb;
102 
103 /* Initialize whiteboard */
104 
silcpurple_wb_init(SilcPurple sg,SilcClientEntry client_entry)105 PurpleWhiteboard *silcpurple_wb_init(SilcPurple sg, SilcClientEntry client_entry)
106 {
107         SilcClientConnection conn;
108 	PurpleWhiteboard *wb;
109 	SilcPurpleWb wbs;
110 
111 	conn = sg->conn;
112 	wb = purple_whiteboard_get_session(sg->account, client_entry->nickname);
113 	if (!wb)
114 		wb = purple_whiteboard_create(sg->account, client_entry->nickname, 0);
115 	if (!wb)
116 		return NULL;
117 
118 	if (!wb->proto_data) {
119 		wbs = silc_calloc(1, sizeof(*wbs));
120 		if (!wbs)
121 			return NULL;
122 		wbs->type = 0;
123 		wbs->u.client = client_entry;
124 		wbs->width = SILCPURPLE_WB_WIDTH;
125 		wbs->height = SILCPURPLE_WB_HEIGHT;
126 		wbs->brush_size = SILCPURPLE_WB_BRUSH_SMALL;
127 		wbs->brush_color = SILCPURPLE_WB_COLOR_BLACK;
128 		wb->proto_data = wbs;
129 
130 		/* Start the whiteboard */
131 		purple_whiteboard_start(wb);
132 		purple_whiteboard_clear(wb);
133 	}
134 
135 	return wb;
136 }
137 
silcpurple_wb_init_ch(SilcPurple sg,SilcChannelEntry channel)138 PurpleWhiteboard *silcpurple_wb_init_ch(SilcPurple sg, SilcChannelEntry channel)
139 {
140 	PurpleWhiteboard *wb;
141 	SilcPurpleWb wbs;
142 
143 	wb = purple_whiteboard_get_session(sg->account, channel->channel_name);
144 	if (!wb)
145 		wb = purple_whiteboard_create(sg->account, channel->channel_name, 0);
146 	if (!wb)
147 		return NULL;
148 
149 	if (!wb->proto_data) {
150 		wbs = silc_calloc(1, sizeof(*wbs));
151 		if (!wbs)
152 			return NULL;
153 		wbs->type = 1;
154 		wbs->u.channel = channel;
155 		wbs->width = SILCPURPLE_WB_WIDTH;
156 		wbs->height = SILCPURPLE_WB_HEIGHT;
157 		wbs->brush_size = SILCPURPLE_WB_BRUSH_SMALL;
158 		wbs->brush_color = SILCPURPLE_WB_COLOR_BLACK;
159 		wb->proto_data = wbs;
160 
161 		/* Start the whiteboard */
162 		purple_whiteboard_start(wb);
163 		purple_whiteboard_clear(wb);
164 	}
165 
166 	return wb;
167 }
168 
169 static void
silcpurple_wb_parse(SilcPurpleWb wbs,PurpleWhiteboard * wb,unsigned char * message,SilcUInt32 message_len)170 silcpurple_wb_parse(SilcPurpleWb wbs, PurpleWhiteboard *wb,
171 		  unsigned char *message, SilcUInt32 message_len)
172 {
173 	SilcUInt8 command;
174 	SilcUInt16 width, height, brush_size;
175 	SilcUInt32 brush_color, x, y, dx, dy;
176 	SilcBufferStruct buf;
177 	int ret;
178 
179 	/* Parse the packet */
180 	silc_buffer_set(&buf, message, message_len);
181 	ret = silc_buffer_unformat(&buf,
182 				   SILC_STR_UI_CHAR(&command),
183 				   SILC_STR_UI_SHORT(&width),
184 				   SILC_STR_UI_SHORT(&height),
185 				   SILC_STR_UI_INT(&brush_color),
186 				   SILC_STR_UI_SHORT(&brush_size),
187 				   SILC_STR_END);
188 	if (ret < 0)
189 		return;
190 	silc_buffer_pull(&buf, ret);
191 
192 	/* Update whiteboard if its dimensions changed */
193 	if (width != wbs->width || height != wbs->height)
194 		silcpurple_wb_set_dimensions(wb, width, height);
195 
196 	if (command == SILCPURPLE_WB_DRAW) {
197 		/* Parse data and draw it */
198 		ret = silc_buffer_unformat(&buf,
199 					   SILC_STR_UI_INT(&dx),
200 					   SILC_STR_UI_INT(&dy),
201 					   SILC_STR_END);
202 		if (ret < 0)
203 			return;
204 		silc_buffer_pull(&buf, 8);
205 		x = dx;
206 		y = dy;
207 		while (buf.len > 0) {
208 			ret = silc_buffer_unformat(&buf,
209 						   SILC_STR_UI_INT(&dx),
210 						   SILC_STR_UI_INT(&dy),
211 						   SILC_STR_END);
212 			if (ret < 0)
213 				return;
214 			silc_buffer_pull(&buf, 8);
215 
216 			purple_whiteboard_draw_line(wb, x, y, x + dx, y + dy,
217 						  brush_color, brush_size);
218 			x += dx;
219 			y += dy;
220 		}
221 	}
222 
223 	if (command == SILCPURPLE_WB_CLEAR)
224 		purple_whiteboard_clear(wb);
225 }
226 
227 typedef struct {
228   unsigned char *message;
229   SilcUInt32 message_len;
230   SilcPurple sg;
231   SilcClientEntry sender;
232   SilcChannelEntry channel;
233 } *SilcPurpleWbRequest;
234 
235 static void
silcpurple_wb_request_cb(SilcPurpleWbRequest req,gint id)236 silcpurple_wb_request_cb(SilcPurpleWbRequest req, gint id)
237 {
238 	PurpleWhiteboard *wb;
239 
240         if (id != 1)
241                 goto out;
242 
243 	if (!req->channel)
244 		wb = silcpurple_wb_init(req->sg, req->sender);
245 	else
246 		wb = silcpurple_wb_init_ch(req->sg, req->channel);
247 
248 	silcpurple_wb_parse(wb->proto_data, wb, req->message, req->message_len);
249 
250   out:
251 	silc_free(req->message);
252 	silc_free(req);
253 }
254 
255 static void
silcpurple_wb_request(SilcClient client,const unsigned char * message,SilcUInt32 message_len,SilcClientEntry sender,SilcChannelEntry channel)256 silcpurple_wb_request(SilcClient client, const unsigned char *message,
257 		    SilcUInt32 message_len, SilcClientEntry sender,
258 		    SilcChannelEntry channel)
259 {
260 	char tmp[128];
261 	SilcPurpleWbRequest req;
262 	PurpleConnection *gc;
263 	SilcPurple sg;
264 
265 	gc = client->application;
266 	sg = gc->proto_data;
267 
268 	/* Open whiteboard automatically if requested */
269 	if (purple_account_get_bool(sg->account, "open-wb", FALSE)) {
270 		PurpleWhiteboard *wb;
271 
272 		if (!channel)
273 			wb = silcpurple_wb_init(sg, sender);
274 		else
275 			wb = silcpurple_wb_init_ch(sg, channel);
276 
277 		silcpurple_wb_parse(wb->proto_data, wb, (unsigned char *)message,
278 				  message_len);
279 		return;
280 	}
281 
282 	/* Close any previous unaccepted requests */
283 	purple_request_close_with_handle(sender);
284 
285 	if (!channel) {
286 		g_snprintf(tmp, sizeof(tmp),
287 			_("%s sent message to whiteboard. Would you like "
288 			  "to open the whiteboard?"), sender->nickname);
289 	} else {
290 		g_snprintf(tmp, sizeof(tmp),
291 			_("%s sent message to whiteboard on %s channel. "
292 			  "Would you like to open the whiteboard?"),
293 			sender->nickname, channel->channel_name);
294 	}
295 
296 	req = silc_calloc(1, sizeof(*req));
297 	if (!req)
298 		return;
299 	req->message = silc_memdup(message, message_len);
300 	req->message_len = message_len;
301 	req->sender = sender;
302 	req->channel = channel;
303 	req->sg = sg;
304 
305 	purple_request_action(sender, _("Whiteboard"), tmp, NULL, 1,
306 				sg->account, sender->nickname, NULL, req, 2,
307 			    _("Yes"), G_CALLBACK(silcpurple_wb_request_cb),
308 			    _("No"), G_CALLBACK(silcpurple_wb_request_cb));
309 }
310 
311 /* Process incoming whiteboard message */
312 
silcpurple_wb_receive(SilcClient client,SilcClientConnection conn,SilcClientEntry sender,SilcMessagePayload payload,SilcMessageFlags flags,const unsigned char * message,SilcUInt32 message_len)313 void silcpurple_wb_receive(SilcClient client, SilcClientConnection conn,
314 			 SilcClientEntry sender, SilcMessagePayload payload,
315 			 SilcMessageFlags flags, const unsigned char *message,
316 			 SilcUInt32 message_len)
317 {
318 	SilcPurple sg;
319         PurpleConnection *gc;
320 	PurpleWhiteboard *wb;
321 	SilcPurpleWb wbs;
322 
323 	gc = client->application;
324         sg = gc->proto_data;
325 
326 	wb = purple_whiteboard_get_session(sg->account, sender->nickname);
327 	if (!wb) {
328 		/* Ask user if they want to open the whiteboard */
329 		silcpurple_wb_request(client, message, message_len,
330 				    sender, NULL);
331 		return;
332 	}
333 
334 	wbs = wb->proto_data;
335 	silcpurple_wb_parse(wbs, wb, (unsigned char *)message, message_len);
336 }
337 
338 /* Process incoming whiteboard message on channel */
339 
silcpurple_wb_receive_ch(SilcClient client,SilcClientConnection conn,SilcClientEntry sender,SilcChannelEntry channel,SilcMessagePayload payload,SilcMessageFlags flags,const unsigned char * message,SilcUInt32 message_len)340 void silcpurple_wb_receive_ch(SilcClient client, SilcClientConnection conn,
341 			    SilcClientEntry sender, SilcChannelEntry channel,
342 			    SilcMessagePayload payload,
343 			    SilcMessageFlags flags,
344 			    const unsigned char *message,
345 			    SilcUInt32 message_len)
346 {
347 	SilcPurple sg;
348         PurpleConnection *gc;
349 	PurpleWhiteboard *wb;
350 	SilcPurpleWb wbs;
351 
352 	gc = client->application;
353         sg = gc->proto_data;
354 
355 	wb = purple_whiteboard_get_session(sg->account, channel->channel_name);
356 	if (!wb) {
357 		/* Ask user if they want to open the whiteboard */
358 		silcpurple_wb_request(client, message, message_len,
359 				    sender, channel);
360 		return;
361 	}
362 
363 	wbs = wb->proto_data;
364 	silcpurple_wb_parse(wbs, wb, (unsigned char *)message, message_len);
365 }
366 
367 /* Send whiteboard message */
368 
silcpurple_wb_send(PurpleWhiteboard * wb,GList * draw_list)369 void silcpurple_wb_send(PurpleWhiteboard *wb, GList *draw_list)
370 {
371 	SilcPurpleWb wbs = wb->proto_data;
372 	SilcBuffer packet;
373 	GList *list;
374 	int len;
375         PurpleConnection *gc;
376         SilcPurple sg;
377 
378 	g_return_if_fail(draw_list);
379 	gc = purple_account_get_connection(wb->account);
380 	g_return_if_fail(gc);
381  	sg = gc->proto_data;
382 	g_return_if_fail(sg);
383 
384 	len = SILCPURPLE_WB_HEADER;
385 	for (list = draw_list; list; list = list->next)
386 		len += 4;
387 
388 	packet = silc_buffer_alloc_size(len);
389 	if (!packet)
390 		return;
391 
392 	/* Assmeble packet */
393 	silc_buffer_format(packet,
394 			   SILC_STR_UI32_STRING(SILCPURPLE_WB_MIME),
395 			   SILC_STR_UI_CHAR(SILCPURPLE_WB_DRAW),
396 			   SILC_STR_UI_SHORT(wbs->width),
397 			   SILC_STR_UI_SHORT(wbs->height),
398 			   SILC_STR_UI_INT(wbs->brush_color),
399 			   SILC_STR_UI_SHORT(wbs->brush_size),
400 			   SILC_STR_END);
401 	silc_buffer_pull(packet, SILCPURPLE_WB_HEADER);
402 	for (list = draw_list; list; list = list->next) {
403 		silc_buffer_format(packet,
404 				   SILC_STR_UI_INT(GPOINTER_TO_INT(list->data)),
405 				   SILC_STR_END);
406 		silc_buffer_pull(packet, 4);
407 	}
408 
409 	/* Send the message */
410 	if (wbs->type == 0) {
411 		/* Private message */
412 		silc_client_send_private_message(sg->client, sg->conn,
413 						 wbs->u.client,
414 						 SILC_MESSAGE_FLAG_DATA,
415 						 packet->head, len, TRUE);
416 	} else if (wbs->type == 1) {
417 		/* Channel message. Channel private keys are not supported. */
418 		silc_client_send_channel_message(sg->client, sg->conn,
419 						 wbs->u.channel, NULL,
420 						 SILC_MESSAGE_FLAG_DATA,
421 						 packet->head, len, TRUE);
422 	}
423 
424 	silc_buffer_free(packet);
425 }
426 
427 /* Purple Whiteboard operations */
428 
silcpurple_wb_start(PurpleWhiteboard * wb)429 void silcpurple_wb_start(PurpleWhiteboard *wb)
430 {
431 	/* Nothing here.  Everything is in initialization */
432 }
433 
silcpurple_wb_end(PurpleWhiteboard * wb)434 void silcpurple_wb_end(PurpleWhiteboard *wb)
435 {
436 	silc_free(wb->proto_data);
437 	wb->proto_data = NULL;
438 }
439 
silcpurple_wb_get_dimensions(const PurpleWhiteboard * wb,int * width,int * height)440 void silcpurple_wb_get_dimensions(const PurpleWhiteboard *wb, int *width, int *height)
441 {
442 	SilcPurpleWb wbs = wb->proto_data;
443 	*width = wbs->width;
444 	*height = wbs->height;
445 }
446 
silcpurple_wb_set_dimensions(PurpleWhiteboard * wb,int width,int height)447 void silcpurple_wb_set_dimensions(PurpleWhiteboard *wb, int width, int height)
448 {
449 	SilcPurpleWb wbs = wb->proto_data;
450 	wbs->width = width > SILCPURPLE_WB_WIDTH_MAX ? SILCPURPLE_WB_WIDTH_MAX :
451 			width;
452 	wbs->height = height > SILCPURPLE_WB_HEIGHT_MAX ? SILCPURPLE_WB_HEIGHT_MAX :
453 			height;
454 
455 	/* Update whiteboard */
456 	purple_whiteboard_set_dimensions(wb, wbs->width, wbs->height);
457 }
458 
silcpurple_wb_get_brush(const PurpleWhiteboard * wb,int * size,int * color)459 void silcpurple_wb_get_brush(const PurpleWhiteboard *wb, int *size, int *color)
460 {
461 	SilcPurpleWb wbs = wb->proto_data;
462 	*size = wbs->brush_size;
463 	*color = wbs->brush_color;
464 }
465 
silcpurple_wb_set_brush(PurpleWhiteboard * wb,int size,int color)466 void silcpurple_wb_set_brush(PurpleWhiteboard *wb, int size, int color)
467 {
468 	SilcPurpleWb wbs = wb->proto_data;
469 	wbs->brush_size = size;
470 	wbs->brush_color = color;
471 
472 	/* Update whiteboard */
473 	purple_whiteboard_set_brush(wb, size, color);
474 }
475 
silcpurple_wb_clear(PurpleWhiteboard * wb)476 void silcpurple_wb_clear(PurpleWhiteboard *wb)
477 {
478 	SilcPurpleWb wbs = wb->proto_data;
479 	SilcBuffer packet;
480 	int len;
481         PurpleConnection *gc;
482         SilcPurple sg;
483 
484 	gc = purple_account_get_connection(wb->account);
485 	g_return_if_fail(gc);
486  	sg = gc->proto_data;
487 	g_return_if_fail(sg);
488 
489 	len = SILCPURPLE_WB_HEADER;
490 	packet = silc_buffer_alloc_size(len);
491 	if (!packet)
492 		return;
493 
494 	/* Assmeble packet */
495 	silc_buffer_format(packet,
496 			   SILC_STR_UI32_STRING(SILCPURPLE_WB_MIME),
497 			   SILC_STR_UI_CHAR(SILCPURPLE_WB_CLEAR),
498 			   SILC_STR_UI_SHORT(wbs->width),
499 			   SILC_STR_UI_SHORT(wbs->height),
500 			   SILC_STR_UI_INT(wbs->brush_color),
501 			   SILC_STR_UI_SHORT(wbs->brush_size),
502 			   SILC_STR_END);
503 
504 	/* Send the message */
505 	if (wbs->type == 0) {
506 		/* Private message */
507 		silc_client_send_private_message(sg->client, sg->conn,
508 						 wbs->u.client,
509 						 SILC_MESSAGE_FLAG_DATA,
510 						 packet->head, len, TRUE);
511 	} else if (wbs->type == 1) {
512 		/* Channel message */
513 		silc_client_send_channel_message(sg->client, sg->conn,
514 						 wbs->u.channel, NULL,
515 						 SILC_MESSAGE_FLAG_DATA,
516 						 packet->head, len, TRUE);
517 	}
518 
519 	silc_buffer_free(packet);
520 }
521