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