1 #include "internal.h"
2 
3 #include "pidgin-encryption-config.h"
4 
5 #include <string.h>
6 #include <gdk/gdk.h>
7 #include <gtk/gtkplug.h>
8 #include <gtk/gtkimagemenuitem.h>
9 
10 #include <gtkplugin.h>
11 #include <gtkmenutray.h>
12 #include <debug.h>
13 #include <gtkimhtml.h>
14 #include <gtklog.h>
15 
16 #include "state_ui.h"
17 #include "state.h"
18 #include "encrypt.h"
19 #include "nls.h"
20 
21 #ifdef _WIN32
22 #include "win32/win32dep.h"
23 #endif
24 
25 /* Icons */
26 /* #include "icon_out_lock.xpm" */
27 /* #include "icon_out_unlock.xpm" */
28 /* #include "icon_out_capable.xpm" */
29 /* #include "icon_in_lock.xpm" */
30 /* #include "icon_in_unlock.xpm" */
31 
32 #define PIXMAP_TX_UNENCRYPTED "Pidgin-Encryption_Out_Unencrypted"
33 #define PIXMAP_TX_CAPABLE "Pidgin-Encryption_Out_Capable"
34 #define PIXMAP_TX_ENCRYPTED "Pidgin-Encryption_Out_Encrypted"
35 #define PIXMAP_RX_UNENCRYPTED "Pidgin-Encryption_In_Unencrypted"
36 #define PIXMAP_RX_ENCRYPTED "Pidgin-Encryption_In_Encrypted"
37 
38 static GHashTable * tx_encrypt_menus = 0;
39 static GHashTable * rx_encrypt_iconlist = 0;
40 static gchar * smiley_filepath = 0;
41 
42 struct _TxMenuButtons {
43    GtkWidget *unencrypted;  /* each is a iconmenu item with one corresponding submenu item */
44    GtkWidget *capable;
45    GtkWidget *encrypted;
46 };
47 typedef struct _TxMenuButtons TxMenuButtons;
48 
49 
50 static struct StockIcon{
51    const char * name;
52    const char * filename;
53 } const stock_icons [] = {
54    { PIXMAP_TX_ENCRYPTED, "icon_out_lock.png" },
55    { PIXMAP_TX_UNENCRYPTED, "icon_out_unlock.png" },
56    { PIXMAP_TX_CAPABLE, "icon_out_capable.png" },
57    { PIXMAP_RX_ENCRYPTED, "icon_in_lock.png" },
58    { PIXMAP_RX_UNENCRYPTED, "icon_in_unlock.png" },
59 };
60 
61 static TxMenuButtons * get_txbuttons_for_win(PidginWindow *win);
62 static GtkIMHtmlSmiley * create_smiley_if_absent(GtkIMHtml *imhtml);
63 static void enable_encrypt_cb(GtkWidget* item, PidginWindow* win);
64 static void disable_encrypt_cb(GtkWidget* item, PidginWindow* win);
65 static void remove_txbuttons_cb( GtkWidget *widget, gpointer data );
66 static void remove_rx_icon_cb( GtkWidget *widget, gpointer data);
67 
68 
69 
70 static TxMenuButtons * get_txbuttons_for_win(PidginWindow *win) {
71    TxMenuButtons *tx_menubuttons;
72    GtkWidget *submenuitem, *menuitem;
73    GtkWidget *menu;
74    GtkWidget *image;
75 
76    tx_menubuttons = g_hash_table_lookup(tx_encrypt_menus, win);
77 
78    if (!tx_menubuttons) {
79       GtkWidget *menubar = win->menu.menubar;
80       int newMenuPos = 0; /* Where to insert our 3 new menu items: at current pos of menu tray */
81 
82       if (menubar == NULL) {
83          return NULL;
84       }
85 
86       {
87          GList * list = gtk_container_get_children(GTK_CONTAINER(menubar));
88          GList * iter = list;
89          while (iter) {
90             if (PIDGIN_IS_MENU_TRAY(iter->data)) {
91                iter = 0;
92             } else {
93                ++newMenuPos;
94                iter = iter->next;
95             }
96          }
97          g_list_free(list);
98       }
99 
100       tx_menubuttons = g_malloc(sizeof(TxMenuButtons));
101 
102 
103       /* 'not capable' icon on menu with "Enable Encryption" as sole menu possibility */
104       menu = gtk_menu_new();
105 
106       submenuitem = gtk_menu_item_new_with_label (_("Enable Encryption"));
107       gtk_menu_shell_append(GTK_MENU_SHELL(menu), submenuitem);
108       gtk_widget_show(submenuitem);
109       g_signal_connect(G_OBJECT(submenuitem), "activate", G_CALLBACK(enable_encrypt_cb), win);
110 
111       image = gtk_image_new_from_stock(PIXMAP_TX_UNENCRYPTED, GTK_ICON_SIZE_MENU);
112       menuitem = gtk_image_menu_item_new_with_label("");
113       gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
114       gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(menuitem), TRUE);
115 
116       gtk_menu_shell_insert(GTK_MENU_SHELL(menubar), menuitem, newMenuPos);
117       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
118       gtk_widget_show(menuitem);
119 
120       tx_menubuttons->unencrypted = menuitem;
121 
122 
123       /* 'capable' icon on menu with "Enable Encryption" as sole menu possibility */
124       menu = gtk_menu_new();
125 
126       submenuitem = gtk_menu_item_new_with_label (_("Enable Encryption"));
127       gtk_menu_shell_append(GTK_MENU_SHELL(menu), submenuitem);
128       gtk_widget_show(submenuitem);
129       g_signal_connect(G_OBJECT(submenuitem), "activate", G_CALLBACK(enable_encrypt_cb), win);
130 
131       image = gtk_image_new_from_stock(PIXMAP_TX_CAPABLE, GTK_ICON_SIZE_MENU);
132 
133       menuitem = gtk_image_menu_item_new_with_label("");
134       gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
135       gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(menuitem), TRUE);
136 
137       gtk_menu_shell_insert(GTK_MENU_SHELL(menubar), menuitem, newMenuPos);
138       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
139       gtk_widget_hide(menuitem);
140 
141       tx_menubuttons->capable = menuitem;
142 
143       /* 'encrypted' icon on menu with "Disable Encryption" as sole menu possibility */
144       menu = gtk_menu_new();
145 
146       submenuitem = gtk_menu_item_new_with_label (_("Disable Encryption"));
147       gtk_menu_shell_append(GTK_MENU_SHELL(menu), submenuitem);
148       gtk_widget_show(submenuitem);
149       g_signal_connect(G_OBJECT(submenuitem), "activate", G_CALLBACK(disable_encrypt_cb), win);
150 
151       image = gtk_image_new_from_stock(PIXMAP_TX_ENCRYPTED, GTK_ICON_SIZE_MENU);
152 
153       menuitem = gtk_image_menu_item_new_with_label("");
154       gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
155       gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(menuitem), TRUE);
156 
157       gtk_menu_shell_insert(GTK_MENU_SHELL(menubar), menuitem, newMenuPos);
158       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
159       gtk_widget_hide(menuitem);
160 
161       tx_menubuttons->encrypted = menuitem;
162 
163       g_hash_table_insert(tx_encrypt_menus, win, tx_menubuttons);
164 
165       g_signal_connect (G_OBJECT(win->window), "destroy", G_CALLBACK(remove_txbuttons_cb), win);
166 
167       purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
168                  "Adding menu item to win %p, item %p\n",
169                  win, tx_menubuttons);
170    }
171    return tx_menubuttons;
172 }
173 
174 static void remove_txbuttons_cb( GtkWidget *widget, gpointer data ) {
175    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
176               "Got callback for destroyed window %p %p\n", data, widget);
177    g_hash_table_remove(tx_encrypt_menus, data);
178 }
179 
180 static void remove_rx_icon_cb( GtkWidget *widget, gpointer data ) {
181    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
182               "Got callback for destroyed window %p %p\n", data, widget);
183    g_hash_table_remove(rx_encrypt_iconlist, data);
184 }
185 
186 void PE_state_ui_init() {
187    smiley_filepath = g_build_filename(DATADIR, "pixmaps", "pidgin-encryption", "crypto.png", NULL);
188 
189    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Smiley Filepath: '%s'\n", smiley_filepath);
190 
191    tx_encrypt_menus = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
192 
193    rx_encrypt_iconlist = g_hash_table_new(g_direct_hash, g_direct_equal);
194 }
195 
196 void PE_state_ui_delete() {
197    g_hash_table_destroy(tx_encrypt_menus);
198    tx_encrypt_menus = NULL;
199 
200    g_hash_table_destroy(rx_encrypt_iconlist);
201    rx_encrypt_iconlist = NULL;
202 
203    g_free(smiley_filepath);
204    smiley_filepath = NULL;
205 }
206 
207 void PE_set_tx_encryption_icon(PurpleConversation* conv,
208                                gboolean do_encrypt, gboolean is_capable) {
209    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
210    PidginWindow *win;
211    TxMenuButtons *buttons;
212 
213    /* we now get called based on conversation changes before the gtkconv has */
214    /* been set up for the conversation.  If that is going on, just bail until */
215    /* things are set up right */
216 
217    if (!gtkconv) return;
218 
219    win = pidgin_conv_get_window(gtkconv);
220    g_return_if_fail(win != NULL);
221 
222    /* ensure that the conv we are adding for is actually the active one */
223    if (pidgin_conv_window_get_active_gtkconv(win)->active_conv != conv) {
224       return;
225    }
226 
227    buttons = get_txbuttons_for_win(win);
228    g_return_if_fail(buttons != NULL);
229 
230    if (do_encrypt) {
231       gtk_widget_hide(buttons->unencrypted);
232       gtk_widget_hide(buttons->capable);
233       gtk_widget_show(buttons->encrypted);
234    } else if (is_capable) {
235       gtk_widget_hide(buttons->unencrypted);
236       gtk_widget_show(buttons->capable);
237       gtk_widget_hide(buttons->encrypted);
238    } else {
239       gtk_widget_show(buttons->unencrypted);
240       gtk_widget_hide(buttons->capable);
241       gtk_widget_hide(buttons->encrypted);
242    }
243 }
244 
245 void PE_set_rx_encryption_icon(PurpleConversation *conv, gboolean encrypted) {
246    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
247    PidginWindow *win;
248 
249    GtkWidget *tray;
250    GtkWidget *rx_encrypted_icon;
251 
252    /* we now get called based on conversation changes before the gtkconv has */
253    /* been set up for the conversation.  If that is going on, just bail until */
254    /* things are set up right */
255 
256    if (!gtkconv) return;
257 
258    win = pidgin_conv_get_window(gtkconv);
259    g_return_if_fail(win != NULL);
260    tray = win->menu.tray;
261 
262    /* ensure that the conv we are adding for is actually the active one */
263    if (pidgin_conv_window_get_active_gtkconv(win)->active_conv != conv) {
264       return;
265    }
266 
267 
268    rx_encrypted_icon = g_hash_table_lookup(rx_encrypt_iconlist, win);
269 
270    if (!rx_encrypted_icon) {
271       rx_encrypted_icon = gtk_image_new_from_stock(PIXMAP_RX_ENCRYPTED, GTK_ICON_SIZE_MENU);
272 
273 	  pidgin_menu_tray_append(PIDGIN_MENU_TRAY(tray), rx_encrypted_icon,
274                               _("The last message received was encrypted  with the Pidgin-Encryption plugin"));
275 
276       g_hash_table_insert(rx_encrypt_iconlist, win, rx_encrypted_icon);
277       g_signal_connect (G_OBJECT(win->window), "destroy", G_CALLBACK(remove_rx_icon_cb), win);
278 
279    } else {
280       purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
281                  "Using pre-existing menu icon for conv %p, win %p, item %p\n",
282                  conv, win, rx_encrypted_icon);
283    }
284 
285    if (encrypted) {
286       gtk_widget_show(rx_encrypted_icon);
287    } else {
288       gtk_widget_hide(rx_encrypted_icon);
289    }
290 }
291 
292 /* returns the new Smiley if created, or NULL if it was already there */
293 static GtkIMHtmlSmiley * create_smiley_if_absent(GtkIMHtml *imhtml) {
294    GtkIMHtmlSmiley * smiley;
295    const char* category = gtk_imhtml_get_protocol_name(imhtml);
296 
297    /* make sure that the category we're about to use to add (based on the protocol name) */
298    /* already exists.  If it doesn't, just use the default category so it isn't created. */
299    if (category && g_hash_table_lookup(imhtml->smiley_data, category) == NULL) {
300       category = NULL;
301    }
302 
303    smiley = gtk_imhtml_smiley_get(imhtml, category, CRYPTO_SMILEY);
304 
305    if (smiley) {
306       /* We're not creating it, because it was already there.  Tell the caller that */
307       return NULL;
308    }
309 
310    /* This may leak.  How does it get cleaned up? */
311    smiley = g_new0(GtkIMHtmlSmiley, 1);
312    smiley->file = smiley_filepath;
313    smiley->smile = CRYPTO_SMILEY;
314    smiley->loader = NULL;
315    smiley->flags  = smiley->flags | GTK_IMHTML_SMILEY_CUSTOM;
316 
317    gtk_imhtml_associate_smiley(imhtml, category, smiley);
318    return smiley;
319 }
320 
321 static void enable_encrypt_cb(GtkWidget* item, PidginWindow* win) {
322    PidginConversation *gtkconv;
323    PurpleConversation *conv;
324 
325    g_return_if_fail(win != NULL);
326    gtkconv = pidgin_conv_window_get_active_gtkconv(win);
327 
328    g_return_if_fail(gtkconv != NULL);
329 
330    conv = gtkconv->active_conv;
331 
332    g_return_if_fail(conv != NULL);
333 
334    purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Enable encryption on conv %p\n", conv);
335    PE_set_tx_encryption(conv, TRUE);
336 }
337 
338 static void disable_encrypt_cb(GtkWidget* item, PidginWindow* win) {
339    PidginConversation *gtkconv;
340    PurpleConversation *conv;
341 
342    g_return_if_fail(win != NULL);
343    gtkconv = pidgin_conv_window_get_active_gtkconv(win);
344 
345    g_return_if_fail(gtkconv != NULL);
346 
347    conv = gtkconv->active_conv;
348 
349    g_return_if_fail(conv != NULL);
350 
351    purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Disable encryption on conv %p\n", conv);
352    PE_set_tx_encryption(conv, FALSE);
353 }
354 
355 void PE_add_smiley(PurpleConversation* conv) {
356    GtkIMHtmlSmiley * smiley;
357    GtkIMHtml * imhtml;
358 
359    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
360 
361    if (!gtkconv) return;
362 
363    create_smiley_if_absent( GTK_IMHTML(gtkconv->entry) );
364 
365    imhtml = GTK_IMHTML(gtkconv->imhtml);
366 
367    smiley = create_smiley_if_absent(imhtml);
368 
369    /* if the smiley was created this time, the value of the new smiley was returned */
370    /* given that, we want to iterate through and use the smiley everywhere          */
371    if (smiley) {
372       GtkTextIter cur_iter, cur_plus_offset_iter;
373       gboolean offset_is_ok;
374       const char* category = gtk_imhtml_get_protocol_name(imhtml);
375 
376       /* Go through the buffer and replace our smiley text with the smiley */
377       gtk_text_buffer_get_start_iter(imhtml->text_buffer, &cur_iter);
378 
379       cur_plus_offset_iter = cur_iter;
380       offset_is_ok = gtk_text_iter_forward_chars(&cur_plus_offset_iter, CRYPTO_SMILEY_LEN);
381       while (offset_is_ok) {
382          char *buffer_text = gtk_text_buffer_get_text(imhtml->text_buffer, &cur_iter,
383                                                       &cur_plus_offset_iter, FALSE);
384          if (strcmp(buffer_text, CRYPTO_SMILEY) == 0) {
385             gtk_text_buffer_delete(imhtml->text_buffer, &cur_iter, &cur_plus_offset_iter);
386             gtk_imhtml_insert_smiley_at_iter(imhtml, category, CRYPTO_SMILEY, &cur_iter);
387          } else {
388             gtk_text_iter_forward_chars(&cur_iter, 1);
389          }
390          cur_plus_offset_iter = cur_iter;
391          offset_is_ok = gtk_text_iter_forward_chars(&cur_plus_offset_iter, CRYPTO_SMILEY_LEN);
392          g_free(buffer_text);
393       }
394    }
395 }
396 
397 void PE_log_displaying_cb(PidginLogViewer *viewer, PurpleLog *log, gpointer data) {
398    create_smiley_if_absent( GTK_IMHTML(viewer->imhtml) );
399 }
400 
401 
402 
403 void PE_remove_decorations(PurpleConversation *conv) {
404    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
405    PidginWindow *win;
406    TxMenuButtons *tx_menubuttons;
407    GtkWidget *rx_encrypted_icon;
408 
409    if (!gtkconv) return;
410 
411    win = pidgin_conv_get_window(gtkconv);
412    g_return_if_fail(win != NULL);
413 
414    /* Remove the destroy callbacks: */
415    g_signal_handlers_disconnect_by_func(G_OBJECT(win->window),
416                                         G_CALLBACK(remove_txbuttons_cb), win);
417 
418    g_signal_handlers_disconnect_by_func(G_OBJECT(win->window),
419                                         G_CALLBACK(remove_rx_icon_cb), win);
420 
421    tx_menubuttons = g_hash_table_lookup(tx_encrypt_menus, win);
422    if (tx_menubuttons) {
423       gtk_widget_destroy(tx_menubuttons->unencrypted);
424       gtk_widget_destroy(tx_menubuttons->encrypted);
425       gtk_widget_destroy(tx_menubuttons->capable);
426       g_hash_table_remove(tx_encrypt_menus, win);
427    }
428 
429    rx_encrypted_icon = g_hash_table_lookup(rx_encrypt_iconlist, win);
430    if (rx_encrypted_icon) {
431       gtk_widget_destroy(rx_encrypted_icon);
432       g_hash_table_remove(rx_encrypt_iconlist, win);
433    }
434 }
435 
436 
437 /* stolen from Pidgin/Gaim's icon factory code: */
438 void
439 PE_stock_init(void)
440 {
441 	static gboolean stock_initted = FALSE;
442 	GtkIconFactory *icon_factory;
443 	size_t i;
444 	GtkWidget *win;
445 
446 	if (stock_initted)
447 		return;
448 
449 	stock_initted = TRUE;
450 
451 	/* Setup the icon factory. */
452 	icon_factory = gtk_icon_factory_new();
453 
454 	gtk_icon_factory_add_default(icon_factory);
455 
456 	/* Er, yeah, a hack, but it works. :) */
457 	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
458 	gtk_widget_realize(win);
459 
460 	for (i = 0; i < G_N_ELEMENTS(stock_icons); i++)
461 	{
462 		GtkIconSource *source;
463 		GtkIconSet *iconset;
464 		gchar *filename;
465 
466       filename = g_build_filename(DATADIR, "pixmaps", "pidgin-encryption", stock_icons[i].filename, NULL);
467 
468       if (filename == NULL)
469          continue;
470 
471       purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
472                    "Adding stock from %s\n", filename);
473 
474       source = gtk_icon_source_new();
475       gtk_icon_source_set_filename(source, filename);
476       gtk_icon_source_set_direction_wildcarded(source, TRUE);
477       gtk_icon_source_set_size_wildcarded(source, TRUE);
478       gtk_icon_source_set_state_wildcarded(source, TRUE);
479 
480       iconset = gtk_icon_set_new();
481       gtk_icon_set_add_source(iconset, source);
482 
483       gtk_icon_source_free(source);
484       g_free(filename);
485 
486       purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
487                    "iconset = %p\n", iconset);
488 
489       gtk_icon_factory_add(icon_factory, stock_icons[i].name, iconset);
490 
491       gtk_icon_set_unref(iconset);
492 	}
493 
494 	gtk_widget_destroy(win);
495 	g_object_unref(G_OBJECT(icon_factory));
496 
497 }
498 
499 
500 void PE_pixmap_init() {
501 
502    PE_stock_init();
503 
504 /*    /\* Here we make a "stock" icon factory to make our icons, and inform GTK *\/ */
505 /*    int i; */
506 /*    GdkPixbuf *pixbuf; */
507 /*    GtkIconSet *icon_set; */
508 
509 /*    static const GtkStockItem items[] = { */
510 /*       { "Pidgin-Encryption_Encrypted", "_GTK!", (GdkModifierType)0, 0, NULL }, */
511 /*       { "Pidgin-Encryption_Unencrypted", "_GTK!", (GdkModifierType)0, 0, NULL }, */
512 /*       { "Pidgin-Encryption_Capable", "_GTK!", (GdkModifierType)0, 0, NULL } */
513 /*    }; */
514 
515 
516 /*    GtkIconFactory *factory; */
517 
518 /*    gtk_stock_add (items, G_N_ELEMENTS (items)); */
519 
520 /*    factory = gtk_icon_factory_new(); */
521 /*    gtk_icon_factory_add_default(factory); */
522 
523 /*    for (i = 0; i < G_N_ELEMENTS(stock_icons); i++) { */
524 /*       pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)item_names[i].xpm_data); */
525 /*       icon_set = gtk_icon_set_new_from_pixbuf (pixbuf); */
526 /*       gtk_icon_factory_add (factory, item_names[i].name, icon_set); */
527 /*       gtk_icon_set_unref (icon_set); */
528 /*       g_object_unref (G_OBJECT (pixbuf)); */
529 /*    } */
530 
531 /*    g_object_unref(factory); */
532 }
533 
534 
535 
536 void PE_error_window(const char* message) {
537    GtkWidget *dialog, *label, *okay_button;
538    dialog = gtk_dialog_new();
539    label = gtk_label_new(message);
540 
541    okay_button = gtk_button_new_with_label(_("Ok"));
542       gtk_signal_connect_object (GTK_OBJECT (okay_button), "clicked",
543                               GTK_SIGNAL_FUNC (gtk_widget_destroy), dialog);
544    gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->action_area),
545                       okay_button);
546 
547    gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox),
548                       label);
549    gtk_widget_show_all (dialog);
550 
551 }
552