1 /*
2  * LaTeX.c
3  * pidgin-latex plugin
4  *
5  * This a plugin for Pidgin to display LaTeX formula in conversation
6  *
7  * PLEASE, send any comment, bug report, etc. to the trackers at sourceforge.net
8  *
9  * Copyright (C) 2006-2007 Benjamin Moll (qjuh@users.sourceforge.net)
10  * some portions : Copyright (C) 2004-2006 Nicolas Schoonbroodt (nicolas@ffsa.be)
11  *                 Copyright (C) 2004-2006 GRIm@ (thegrima@altern.org).
12  * 		   Copyright (C) 2004-2006 Eric Betts (bettse@onid.orst.edu).
13  * Windows port  : Copyright (C) 2005-2006 Nicolai Stange (nic-stange@t-online.de)
14  * Other portions heavily inspired and copied from gaim sources
15  * Copyright (C) 1998-2007 Pidgin developers pidgin.im
16  *
17  * This program is free software; you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation; This document is under the scope of
20  * the version 2 of the License, or any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program (see COPYING); if not, write to the Free Software
29  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
30  *
31  */
32 #include "LaTeX.h"
33 
34 #include <stdio.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #ifndef MAX
39 #define MAX(a,b) ( (a) > (b) ? (a ): (b))
40 #endif
41 
42 #ifdef _WIN32
43 #include <windows.h>
44 #endif
45 
46 static void
open_log(PurpleConversation * conv)47 open_log(PurpleConversation *conv)
48 {
49 	conv->logs = g_list_append(NULL, purple_log_new(conv->type == PURPLE_CONV_TYPE_CHAT ? PURPLE_LOG_CHAT :
50 							   PURPLE_LOG_IM, conv->name, conv->account,
51 							   conv, time(NULL), NULL));
52 }
53 
54 #ifdef _WIN32
win32_purple_notify_error(char * prep)55 void win32_purple_notify_error(char *prep)
56 {
57   char *errmsg=NULL;
58   char *finalmsg=NULL;
59   if(!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (void*)&errmsg, 0, NULL))
60   {
61       purple_notify_error(NULL, "LaTeX" ,"Can't display error message.", NULL);
62       return;
63   }
64 
65   if(prep)
66   {
67     finalmsg=malloc((strlen(errmsg)+ strlen(prep) + 3)*sizeof(char));
68     if(!finalmsg)
69     {
70       purple_notify_error(NULL, "LaTeX", "Can't display error message.", NULL);
71       LocalFree(errmsg); /* we can't do anything more for you */
72       return;
73     }
74     strcpy(finalmsg, prep);
75     strcat(finalmsg, ": ");
76     strcat(finalmsg, errmsg);
77     LocalFree(errmsg);
78   }
79   else
80   {
81     finalmsg = malloc((strlen(errmsg)+1)*sizeof(char));
82     if(!finalmsg)
83     {
84       purple_notify_error(NULL, "LaTeX", "Can't display error message.", NULL);
85       LocalFree(errmsg); /* we can't do anything more for you */
86       return;
87     }
88     strcpy(finalmsg, errmsg);
89     LocalFree(errmsg);
90   }
91   purple_notify_error(NULL, "LaTeX", finalmsg, NULL);
92   free(finalmsg);
93   return;
94 }
95 #endif
96 
getdirname(const char const * file)97 static char* getdirname(const char const *file)
98 {
99   char *s=NULL;
100   char *r=NULL;
101   s=strrchr(file, G_DIR_SEPARATOR);
102   if(!s) /* just a pure filename without dir? */
103   {
104     /* Here is no standard-, but GNU-bahaviour of getcwd assumed.
105        Note that msdn.microsoft.com defines the same as GNU.
106      */
107     return getcwd(NULL,0);
108   }
109 
110   s+=1; /* get the G_DIR_SEPARATOR at the end of directory-string */
111   r=malloc(s-file+sizeof(char));
112   if(r)
113   {
114     memcpy(r,file, s-file);
115     r[(s-file)/sizeof(char)]='\0';
116   }
117   return r;
118 }
119 
getfilename(const char const * file)120 static char* getfilename(const char const *file)
121 {
122   char *s=NULL;
123   char *r=NULL;
124   s=strrchr(file,G_DIR_SEPARATOR);
125   if(!s)
126   {
127     r=malloc((strlen(file)+1)*sizeof(char));
128     strcpy(r,file);
129     return r;
130   }
131 
132   s+=1;
133   r=malloc((strlen(file)+1)*sizeof(char)+file-s);
134   if(r)
135   {
136     memcpy(r,s,strlen(file)*sizeof(char)+file-s);
137     r[strlen(file)+(file-s)/sizeof(char)]='\0';
138   }
139   return r;
140 }
141 
searchPATH(const char const * file)142 char* searchPATH(const char const *file)
143 {
144   char *cmd=NULL;
145 #ifdef _WIN32
146   DWORD sz=0;
147   DWORD sz2=0;
148 
149   sz=SearchPath(NULL, file, ".exe", 0, cmd, NULL);
150   cmd = malloc((sz+1)*sizeof(TCHAR));
151   if(cmd)
152   {
153     sz2=SearchPath(NULL, file, ".exe", sz+1, cmd, NULL);
154     if(!sz2)
155     {
156       free(cmd);
157       cmd=NULL;
158     }
159   }
160 #else
161   cmd=malloc((strlen(file)+1)*sizeof(char));
162   if(cmd)
163     strcpy(cmd, file);
164 #endif
165 
166   return cmd;
167 }
168 
169 #ifdef _WIN32 /* we could take system(), too, but this opens ugly console-windows within IM-Session*/
execute(char * cmd,char * opts[],int copts)170 static DWORD execute(char *cmd, char *opts[], int copts)
171 {
172   int i=0;
173   int len=strlen(cmd) + 4;
174   char *params=NULL;
175   DWORD exitcode=0;
176   for(i=0; i<copts; ++i)
177     len+=(strlen(opts[i]))*sizeof(char);
178 
179   params=malloc(len);
180   if(!params)
181     return -1;
182 
183   strcpy(params, "\"");
184   strcat(params, cmd);
185   strcat(params, "\" ");
186 
187   for(i=0; i<copts; ++i)
188     strcat(params, opts[i]);
189 
190   STARTUPINFO sup;
191   PROCESS_INFORMATION pi;
192   ZeroMemory( &sup, sizeof(sup) );
193   sup.cb = sizeof(sup);
194   ZeroMemory( &pi, sizeof(pi) );
195 
196   if(!CreateProcess(NULL, params, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &sup, &pi))
197   {
198     win32_purple_notify_error(cmd);
199     free(params);
200     return -1;
201   }
202 
203   free(params);
204   if(WAIT_OBJECT_0!=WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE))
205   {
206     win32_purple_notify_error(cmd);
207     CloseHandle(pi.hProcess);
208     CloseHandle(pi.hThread);
209     return -1;
210   }
211 
212   if(!GetExitCodeThread(pi.hThread, &exitcode))
213   {
214     win32_purple_notify_error(cmd);
215     return -1;
216   }
217   CloseHandle(pi.hProcess);
218   CloseHandle(pi.hThread);
219   return exitcode;
220 }
221 #else /* not windows, take system() */
execute(char * cmd,char * opts[],int copts)222 static int execute(char *cmd, char *opts[], int copts)
223 {
224   int i=0;
225   int exitcode=-1;
226   int len=strlen(cmd) + 4;
227   char *params=NULL;
228 
229   for(i=0; i<copts; ++i)
230     len+=(strlen(opts[i]))*sizeof(char);
231 
232   params=malloc(len);
233   if(!params)
234     return -1;
235 
236   strcpy(params, "\"");
237   strcat(params, cmd);
238   strcat(params, "\" ");
239 
240   for(i=0; i<copts; ++i)
241     strcat(params, opts[i]);
242 
243   exitcode=system(params);
244   free(params);
245   return exitcode;
246 }
247 #endif
248 
get_latex_cmd()249 static char* get_latex_cmd()
250 {
251   return searchPATH("latex");
252 }
253 
get_dvips_cmd()254 static char* get_dvips_cmd()
255 {
256   return searchPATH("dvips");
257 }
258 
get_convert_cmd()259 static char* get_convert_cmd()
260 {
261 #ifdef _WIN32
262     /*open registry*/
263     DWORD type;
264     DWORD cbData;
265     char *pData=NULL;
266     HKEY hk=NULL;
267     RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\ImageMagick\\Current",0,KEY_READ,&hk);
268     RegQueryValueEx(hk, "BinPath", NULL, &type, NULL, &cbData);
269     cbData=cbData + (1+12)*sizeof(char);
270     pData=malloc(cbData);
271     if(pData && RegQueryValueEx(hk, "BinPath", NULL, &type, pData, &cbData)==ERROR_SUCCESS)
272     {
273       CloseHandle(hk);
274       strcat(pData,"\\convert.exe");
275       return pData;
276     }
277 
278     CloseHandle(hk);
279     free(pData);
280     return searchPATH("convert");
281 #else
282   return searchPATH("convert");
283 #endif
284 }
285 
is_blacklisted(char * message)286 static gboolean is_blacklisted(char *message)
287 {
288   char *not_secure[NB_BLACKLIST] = BLACKLIST;
289   int i;
290   for (i = 0 ; i < NB_BLACKLIST ; i++)
291     {
292       char *begin_not_secure = malloc((strlen(not_secure[i])+9)*sizeof(char));
293       strcpy(begin_not_secure,"\\begin{");
294       strcat(begin_not_secure,not_secure[i]+0x01);
295       strcat(begin_not_secure,"}");
296       if (strstr(message, not_secure[i]) != NULL || strstr(message, begin_not_secure)) return TRUE;
297     }
298   return FALSE;
299 }
300 
latex_to_image(char * latex,char ** file_tex,char ** file_dvi,char ** file_ps,char ** file_png)301 static gboolean latex_to_image(char *latex, char **file_tex, char **file_dvi, char **file_ps, char **file_png)
302 {
303   FILE * texfile;
304   char *file_tmp = NULL;
305   char *tmpdir = NULL;
306   char *cmdlatex = NULL;
307   char *cmddvips = NULL;
308   char *cmdconvert = NULL;
309 
310   /* the following is new and related to temporary-filename-generation */
311   texfile = purple_mkstemp(&file_tmp,TRUE);
312   *file_tex=malloc((strlen(file_tmp)+5)*sizeof(char));
313   *file_dvi=malloc((strlen(file_tmp)+5)*sizeof(char));
314   *file_ps=malloc((strlen(file_tmp)+4)*sizeof(char));
315   *file_png=malloc((strlen(file_tmp)+5)*sizeof(char));
316   if(!(file_tmp && *file_tex && *file_dvi && *file_ps && *file_png))
317   {
318     fclose(texfile);
319     unlink(file_tmp);
320     free(file_tmp);
321     free(*file_tex);
322     free(*file_dvi);
323     free(*file_ps);
324     free(*file_png);
325     *file_tex=*file_dvi=*file_ps=*file_png=NULL;
326     return FALSE;
327   }
328   strcpy(*file_tex, file_tmp);
329   strcat(*file_tex, ".tex");
330   strcpy(*file_dvi, file_tmp);
331   strcat(*file_dvi, ".dvi");
332   strcpy(*file_ps, file_tmp);
333   strcat(*file_ps, ".ps");
334   strcpy(*file_png, file_tmp);
335   strcat(*file_png, ".jpg");
336   free(file_tmp);
337   fclose(texfile);
338 
339   if (! (texfile = fopen(*file_tex, "w"))) return FALSE;
340   fprintf(texfile, HEADER HEADER_MATH "%s" FOOTER_MATH FOOTER, latex);
341   fclose (texfile);
342 
343   tmpdir=getdirname(*file_tex);
344   /* generate commands, also new */
345   char *latexopts[5]={"--interaction=nonstopmode", " ", "\"", *file_tex, "\""};
346   char *dvipsopts[8]={"-E", " ", "-o", " \"", *file_ps, "\" \"", *file_dvi, "\""};
347   char *convertopts[5]={"\"", *file_ps, "\" \"", *file_png, "\""};
348   cmdlatex=get_latex_cmd();
349   cmddvips=get_dvips_cmd();
350   cmdconvert=get_convert_cmd();
351 
352   if (!tmpdir || chdir(tmpdir) || !cmdlatex || !cmddvips || !cmdconvert)
353   {
354     free(*file_dvi);
355     free(*file_ps);
356     free(*file_png);
357     *file_dvi=*file_ps=*file_png=NULL;
358     free(cmdlatex);
359     free(cmddvips);
360     free(cmdconvert);
361     return FALSE;
362   }
363 
364   free(tmpdir);
365   if((execute(cmdlatex, latexopts, 5) || execute(cmddvips, dvipsopts, 8) ||  execute(cmdconvert, convertopts, 5))) return FALSE;
366 
367   free(cmdlatex);
368   free(cmddvips);
369   free(cmdconvert);
370 
371   return TRUE;
372 }
373 
analyse(PurpleConversation * conv,char ** tmp2,char * startdelim,char * enddelim,gboolean remote)374 static gboolean analyse(PurpleConversation *conv, char **tmp2, char *startdelim, char *enddelim, gboolean remote)
375 {
376   int pos1, pos2, idimg;
377   char *ptr1, *ptr2;
378   char *file_tex=NULL;
379   char *file_dvi=NULL;
380   char *file_ps=NULL;
381   char *file_png=NULL;
382 
383   ptr1 = strstr(*tmp2, startdelim);
384   while(ptr1 != NULL)
385     {
386       char *tex, *message, *filter, *idstring;
387       gchar *name, *buf, *filedata;
388       size_t size;
389       GError *error = NULL;
390 
391       pos1 = strlen(*tmp2) - strlen(ptr1);
392 
393       // Have to ignore the first 2 char ("$$") --> & [2]
394       ptr2 = strstr(&ptr1[strlen(startdelim)], enddelim);
395       if (ptr2 == NULL)
396 	{ return FALSE; }
397 
398       pos2 = strlen(*tmp2) - strlen(ptr2) + strlen(enddelim);
399 
400       if ((tex = malloc(pos2 - pos1 - strlen(enddelim) - strlen(startdelim) + 1)) == NULL)
401 	{
402 	  // TODO: Report the error
403 	  return FALSE;
404 	}
405 
406       strncpy(tex, &ptr1[strlen(startdelim)], pos2 - pos1 - strlen(startdelim)-strlen(enddelim));
407       tex[pos2-pos1-strlen(startdelim)-strlen(enddelim)] = '\0';
408 
409       // Pidgin transform & to &amp; and I make the inverse transformation
410       while ( (filter = strstr(tex, FILTER_AND) ) != NULL)
411 	{
412 	  strcpy(&tex[strlen(tex) - strlen(filter) + 1], &filter[5]);
413 	}
414       // <br> filter
415       while ( (filter = strstr(tex, FILTER_BR) ) != NULL)
416 	{
417 	  strcpy(&tex[strlen(tex) - strlen(filter)], &filter[4]);
418 	}
419 
420       // Creates the image in file_png
421       if (!latex_to_image(tex, &file_tex, &file_dvi, &file_ps, &file_png)) { free(tex); return FALSE; };
422       free(tex);
423 
424       // loading image
425        if (!g_file_get_contents(file_png, &filedata, &size, &error))
426 	{
427 	  purple_notify_error(NULL, "LaTeX", error->message, NULL);
428 	  g_error_free(error);
429 	  return FALSE;
430 	}
431 
432       unlink(file_tex);
433       file_tex[strlen(file_tex)-4]='\0';
434 
435       strcat(file_tex, ".aux");
436       unlink(file_tex);
437       file_tex[strlen(file_tex)-4]='\0';
438       strcat(file_tex, ".log");
439       unlink(file_tex);
440       unlink(file_dvi);
441       unlink(file_ps);
442       unlink(file_png);
443 
444       free(file_tex);
445       free(file_dvi);
446       free(file_ps);
447       free(file_png);
448 
449 
450       name = "pidginTeX.jpg";
451 
452       idimg = purple_imgstore_add_with_id(filedata, MAX(1024,size), name);
453       filedata = NULL;
454 
455       if (idimg == 0)
456 	{
457 	  buf = g_strdup_printf("Failed to store image.");
458 	  purple_notify_error(NULL,"LaTeX", buf, NULL);
459 	  g_free(buf);
460 	  return FALSE;
461 	}
462 
463       idstring = malloc(10);
464       sprintf(idstring, "%d", idimg);
465 
466       // making new message
467       if ((message = malloc (strlen(*tmp2) - pos2 + pos1 + strlen(idstring) + strlen(IMG_BEGIN) + strlen(IMG_END) + 1)) == NULL)
468 	{
469 	  purple_notify_error(NULL,"LaTeX", "couldn't make the message.", NULL);
470 	  return FALSE;
471 	}
472 
473       if (pos1 > 0)
474 	{
475 	  strncpy(message, *tmp2, pos1);
476 	  message[pos1] = '\0';
477 	  strcat(message, IMG_BEGIN);
478 	} else  {
479 	  strcpy(message, IMG_BEGIN);
480 	}
481       strcat(message, idstring);
482       strcat(message, IMG_END);
483 
484       free(idstring);
485 
486       if (pos2 < strlen(*tmp2))
487       { strcat(message, &ptr2[strlen(enddelim)]); }
488 
489       free(*tmp2);
490       if ((*tmp2 = malloc(strlen(message)+1)) == NULL)
491       {
492 	purple_notify_error(NULL,"LaTeX", "couldn't split the message.", NULL);
493 	return FALSE;
494       }
495 
496       strcpy(*tmp2, message);
497       free(message);
498 
499       ptr1 = strstr(*tmp2, startdelim);
500     }
501   return TRUE;
502 }
503 
pidgin_latex_write(PurpleConversation * conv,char * nom,char * message,PurpleMessageFlags messFlag,char * original)504 static gboolean pidgin_latex_write(PurpleConversation *conv, char *nom, char *message, PurpleMessageFlags messFlag, char *original)
505 {
506   gboolean logflag;
507 
508   // writing log
509   logflag = purple_conversation_is_logging(conv);
510 
511   if (logflag)
512     {
513       GList *log;
514 
515       if (conv->logs == NULL)
516         open_log(conv);
517 
518       log = conv->logs;
519       while (log != NULL) {
520         purple_log_write((PurpleLog *)log->data, messFlag, nom, time(NULL), original);
521         log = log->next;
522       }
523       purple_conversation_set_logging(conv,FALSE);
524     }
525 
526   purple_conv_im_write(PURPLE_CONV_IM(conv), nom, message, messFlag, time(NULL));
527 
528   if (logflag)
529     purple_conversation_set_logging(conv,TRUE);
530 
531   return FALSE;
532 }
533 
message_send(PurpleAccount * account,const char * who,char ** buffer,PurpleConversation * conv,PurpleMessageFlags flags)534 static gboolean message_send(PurpleAccount *account, const char *who, char **buffer, PurpleConversation *conv, PurpleMessageFlags flags)
535 {
536   char *tmp2;
537   int t[10], i = 0;//, j;
538 
539   // if nothing to do
540   if (/*strstr(*buffer, BEG) == NULL &&*/ strstr(*buffer,KOPETE_TEX) == NULL)
541     {
542       return FALSE;
543     };
544 
545   if (is_blacklisted(*buffer)) return FALSE;
546 
547   if((tmp2 = malloc(strlen(*buffer)+1)) == NULL)
548     {
549       // TODO: Notify Error
550       return FALSE;
551     }
552 
553   strcpy(tmp2,*buffer);
554 
555   if (analyse(conv, &tmp2, KOPETE_TEX, KOPETE_TEX, FALSE) )
556     {
557       char *name2;
558 
559       // TODO : THIS IS VERY VERY UGLY
560       // TODO : MODIFY TO MINIMIZE CALL
561       // finding name of sender
562       if (purple_account_get_alias(account) != NULL)
563 	{
564 	  name2 = malloc(strlen(purple_account_get_alias(account))+1);
565 	  strcpy(name2, purple_account_get_alias(account));
566 	}
567       else	if (purple_account_get_username(account) != NULL)
568 	{
569 	  name2 = malloc(strlen(purple_account_get_username(account))+1);
570 	  strcpy(name2,purple_account_get_username(account));
571 	} else {
572 	  free(tmp2);
573 	  return FALSE;
574 	}
575 
576       pidgin_latex_write(conv, name2, tmp2, PURPLE_MESSAGE_SEND, *buffer);
577 
578       free(tmp2);
579       free(name2);
580 
581       return TRUE;
582     }
583 
584   free(tmp2);
585 
586   return FALSE;
587 }
588 
message_recv(PurpleAccount * account,char ** sender,char ** buffer,PurpleConversation * conv,PurpleMessageFlags * flags)589 static gboolean message_recv(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags)
590 {
591 
592   // if no $$ -> nothing to do
593   if (strstr(*buffer, BEG) == NULL && strstr(*buffer, KOPETE_TEX) == NULL)
594     {
595       return FALSE;
596     };
597 
598   if (is_blacklisted(*buffer)) return FALSE;
599 
600   if (conv==NULL) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,account,*sender);
601   if (purple_conversation_get_im_data(conv) != NULL)
602     {
603       char *tmp2;
604       int t[10], i = 0;//, j;
605 
606       if ((tmp2 = malloc(strlen(*buffer)+1)) == NULL)
607 	{
608 	  // TODO: Report the error
609 	  return FALSE;
610 	}
611 
612       strcpy(tmp2,*buffer);
613 
614       if (analyse(conv, &tmp2, KOPETE_TEX, KOPETE_TEX, TRUE));
615       {
616       	pidgin_latex_write(conv, *sender, tmp2, PURPLE_MESSAGE_RECV, *buffer);
617 
618 	free(tmp2); tmp2 = NULL;
619 	free(*buffer);
620 	*buffer = NULL;
621 	return TRUE;
622       }
623 
624       free(tmp2);
625       return FALSE;
626     }
627 
628   return FALSE;
629 }
630 
631 
plugin_load(PurplePlugin * plugin)632 static gboolean plugin_load(PurplePlugin *plugin)
633 {
634   void *conv_handle = purple_conversations_get_handle();
635 
636   purple_signal_connect(conv_handle, "writing-im-msg",
637 		      plugin, PURPLE_CALLBACK(message_send), NULL);
638 
639   purple_signal_connect_priority(conv_handle, "receiving-im-msg",
640 		      plugin, PURPLE_CALLBACK(message_recv), NULL, -9999);
641   purple_debug(PURPLE_DEBUG_INFO, "LaTeX", "LaTeX loaded\n");
642 
643   return TRUE;
644 }
645 
plugin_unload(PurplePlugin * plugin)646 static gboolean plugin_unload(PurplePlugin * plugin)
647 {
648   void *conv_handle = purple_conversations_get_handle();
649 
650   purple_signal_disconnect(conv_handle, "writing-im-msg", plugin, PURPLE_CALLBACK(message_send));
651   purple_signal_disconnect(conv_handle, "receiving-im-msg", plugin, PURPLE_CALLBACK(message_recv));
652 
653   return TRUE;
654 }
655 
656 
657 static PurplePluginInfo info =
658   {
659     PURPLE_PLUGIN_MAGIC,
660     PURPLE_MAJOR_VERSION,
661     PURPLE_MINOR_VERSION,
662     PURPLE_PLUGIN_STANDARD,                             /**< type           */
663     NULL,                                             /**< ui_requirement */
664     0,                                                /**< flags          */
665     NULL,                                             /**< dependencies   */
666     PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
667 
668     LATEX_PLUGIN_ID,                                  /**< id             */
669     "LaTeX",                                      /**< name           */
670     "1.0",                                        /**< version        */
671     /**  summary        */
672     "To display LaTeX formula into Pidgin conversation.",
673     /**  description    */
674     "Put LaTeX-code between $$ ... $$ markup to have it displayed as Picture in your conversation.\nRemember that your contact needs an similar plugin or else he will just see the pure LaTeX-code\nYou must have LaTeX and ImageMagick installed (in your PATH)",
675     "Benjamin Moll <qjuh@users.sourceforge.net>\nNicolas Schoonbroodt <nicolas@ffsa.be>\nNicolai Stange <nic-stange@t-online.de>",   /**< author       */
676     WEBSITE,                                          /**< homepage       */
677     plugin_load,                                      /**< load           */
678     plugin_unload,                                    /**< unload         */
679     NULL,                                             /**< destroy        */
680     NULL,                                             /**< ui_info        */
681     NULL,                                             /**< extra_info     */
682     NULL,
683     NULL,
684     NULL,
685     NULL,
686     NULL,
687     NULL
688   };
689 
690 
691 
692 static void
init_plugin(PurplePlugin * plugin)693 init_plugin(PurplePlugin *plugin)
694 {
695 }
696 
697 PURPLE_INIT_PLUGIN(LaTeX, init_plugin, info)
698