Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / pidgin / plugins / xmppconsole.c
blobc999a52fdf9621357bb7179e505fa83db1f10142
1 /*
2 * Purple - XMPP debugging tool
4 * Copyright (C) 2002-2003, Sean Egan
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
23 #include "gtkplugin.h"
24 #include "version.h"
25 #include "protocol.h"
26 #include "xmlnode.h"
28 #include "gtkwebview.h"
29 #include "gtkutils.h"
31 #include <gdk/gdkkeysyms.h>
33 #include "gtk3compat.h"
35 #define PLUGIN_ID "gtk-xmpp"
36 #define PLUGIN_DOMAIN (g_quark_from_static_string(PLUGIN_ID))
38 typedef struct {
39 PurpleConnection *gc;
40 GtkWidget *window;
41 GtkWidget *hbox;
42 GtkWidget *dropdown;
43 GtkWidget *webview;
44 GtkWidget *entry;
45 GtkWidget *sw;
46 int count;
47 GList *accounts;
48 } XmppConsole;
50 XmppConsole *console = NULL;
51 static void *xmpp_console_handle = NULL;
53 static const gchar *xmpp_prpls[] = {
54 "prpl-jabber", "prpl-gtalk", NULL
57 #define EMPTY_HTML \
58 "<html><head><style type='text/css'>" \
59 "body { word-wrap: break-word; margin: 0; }" \
60 "div.tab { padding-left: 1em; }" \
61 "div.info { color: #777777; }" \
62 "div.incoming { background-color: #ffcece; }" \
63 "div.outgoing { background-color: #dcecc4; }" \
64 "span.bracket { color: #940f8c; }" \
65 "span.tag { color: #8b1dab; font-weight: bold; }" \
66 "span.attr { color: #a02961; font-weight: bold; }" \
67 "span.value { color: #324aa4; }" \
68 "span.xmlns { color: #2cb12f; font-weight: bold;}" \
69 "</style></head></html>"
71 static gboolean
72 xmppconsole_is_xmpp_account(PurpleAccount *account)
74 const gchar *prpl_name;
75 int i;
77 prpl_name = purple_account_get_protocol_id(account);
79 i = 0;
80 while (xmpp_prpls[i] != NULL) {
81 if (g_strcmp0(xmpp_prpls[i], prpl_name) == 0)
82 return TRUE;
83 i++;
86 return FALSE;
89 static char *
90 purple_xmlnode_to_pretty_str(PurpleXmlNode *node, int *len)
92 GString *text = g_string_new("");
93 PurpleXmlNode *c;
94 char *node_name, *esc, *esc2;
95 gboolean need_end = FALSE, pretty = TRUE;
97 g_return_val_if_fail(node != NULL, NULL);
99 node_name = g_markup_escape_text(node->name, -1);
100 g_string_append_printf(text,
101 "<span class=bracket>&lt;</span>"
102 "<span class=tag>%s</span>",
103 node_name);
105 if (node->xmlns) {
106 if ((!node->parent ||
107 !node->parent->xmlns ||
108 strcmp(node->xmlns, node->parent->xmlns)) &&
109 strcmp(node->xmlns, "jabber:client"))
111 char *xmlns = g_markup_escape_text(node->xmlns, -1);
112 g_string_append_printf(text,
113 " <span class=attr>xmlns</span>="
114 "'<span class=xmlns>%s</span>'",
115 xmlns);
116 g_free(xmlns);
119 for (c = node->child; c; c = c->next)
121 if (c->type == PURPLE_XMLNODE_TYPE_ATTRIB) {
122 esc = g_markup_escape_text(c->name, -1);
123 esc2 = g_markup_escape_text(c->data, -1);
124 g_string_append_printf(text,
125 " <span class=attr>%s</span>="
126 "'<span class=value>%s</span>'",
127 esc, esc2);
128 g_free(esc);
129 g_free(esc2);
130 } else if (c->type == PURPLE_XMLNODE_TYPE_TAG || c->type == PURPLE_XMLNODE_TYPE_DATA) {
131 if (c->type == PURPLE_XMLNODE_TYPE_DATA)
132 pretty = FALSE;
133 need_end = TRUE;
137 if (need_end) {
138 g_string_append_printf(text,
139 "<span class=bracket>&gt;</span>%s",
140 pretty ? "<br>" : "");
142 need_end = FALSE;
143 for (c = node->child; c; c = c->next)
145 if (c->type == PURPLE_XMLNODE_TYPE_TAG) {
146 int esc_len;
147 esc = purple_xmlnode_to_pretty_str(c, &esc_len);
148 if (!need_end) {
149 g_string_append(text, "<div class=tab>");
150 need_end = TRUE;
152 text = g_string_append_len(text, esc, esc_len);
153 g_free(esc);
154 } else if (c->type == PURPLE_XMLNODE_TYPE_DATA && c->data_sz > 0) {
155 esc = g_markup_escape_text(c->data, c->data_sz);
156 text = g_string_append(text, esc);
157 g_free(esc);
161 if (need_end)
162 g_string_append(text, "</div>");
164 g_string_append_printf(text,
165 "<span class=bracket>&lt;</span>/"
166 "<span class=tag>%s</span>"
167 "<span class=bracket>&gt;</span><br>",
168 node_name);
169 } else {
170 g_string_append_printf(text,
171 "/<span class=bracket>&gt;</span><br>");
174 g_free(node_name);
176 if (len)
177 *len = text->len;
179 return g_string_free(text, FALSE);
182 static void
183 purple_xmlnode_received_cb(PurpleConnection *gc, PurpleXmlNode **packet, gpointer null)
185 char *str, *formatted;
187 if (!console || console->gc != gc)
188 return;
189 str = purple_xmlnode_to_pretty_str(*packet, NULL);
190 formatted = g_strdup_printf("<div class=incoming>%s</div>", str);
191 pidgin_webview_append_html(PIDGIN_WEBVIEW(console->webview), formatted);
192 g_free(formatted);
193 g_free(str);
196 static void
197 purple_xmlnode_sent_cb(PurpleConnection *gc, char **packet, gpointer null)
199 char *str;
200 char *formatted;
201 PurpleXmlNode *node;
203 if (!console || console->gc != gc)
204 return;
205 node = purple_xmlnode_from_str(*packet, -1);
207 if (!node)
208 return;
210 str = purple_xmlnode_to_pretty_str(node, NULL);
211 formatted = g_strdup_printf("<div class=outgoing>%s</div>", str);
212 pidgin_webview_append_html(PIDGIN_WEBVIEW(console->webview), formatted);
213 g_free(formatted);
214 g_free(str);
215 purple_xmlnode_free(node);
218 static gboolean
219 message_send_cb(GtkWidget *widget, GdkEventKey *event, gpointer p)
221 PurpleProtocol *protocol = NULL;
222 PurpleConnection *gc;
223 gchar *text;
225 if (event->keyval != GDK_KEY_KP_Enter && event->keyval != GDK_KEY_Return)
226 return FALSE;
228 gc = console->gc;
230 if (gc)
231 protocol = purple_connection_get_protocol(gc);
233 text = pidgin_webview_get_body_text(PIDGIN_WEBVIEW(widget));
235 if (protocol)
236 purple_protocol_server_iface_send_raw(protocol, gc, text, strlen(text));
238 g_free(text);
239 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(console->entry), "");
241 return TRUE;
244 static void
245 entry_changed_cb(GtkWidget *webview, void *data)
247 char *xmlstr, *str;
248 #if 0
249 int wrapped_lines;
250 int lines;
251 GdkRectangle oneline;
252 int height;
253 int pad_top, pad_inside, pad_bottom;
254 #endif
255 PurpleXmlNode *node;
257 #if 0
258 /* TODO WebKit: Do entry auto-sizing... */
259 wrapped_lines = 1;
260 gtk_text_buffer_get_start_iter(buffer, &iter);
261 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(console->entry), &iter, &oneline);
262 while (gtk_text_view_forward_display_line(GTK_TEXT_VIEW(console->entry), &iter))
263 wrapped_lines++;
265 lines = gtk_text_buffer_get_line_count(buffer);
267 /* Show a maximum of 64 lines */
268 lines = MIN(lines, 6);
269 wrapped_lines = MIN(wrapped_lines, 6);
271 pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(console->entry));
272 pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(console->entry));
273 pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(console->entry));
275 height = (oneline.height + pad_top + pad_bottom) * lines;
276 height += (oneline.height + pad_inside) * (wrapped_lines - lines);
278 gtk_widget_set_size_request(console->sw, -1, height + 6);
279 #endif
281 str = pidgin_webview_get_body_text(PIDGIN_WEBVIEW(webview));
282 if (!str)
283 return;
284 xmlstr = g_strdup_printf("<xml>%s</xml>", str);
285 node = purple_xmlnode_from_str(xmlstr, -1);
286 if (node) {
287 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(console->entry));
288 } else {
289 pidgin_webview_toggle_backcolor(PIDGIN_WEBVIEW(console->entry), "#ffcece");
291 g_free(str);
292 g_free(xmlstr);
293 if (node)
294 purple_xmlnode_free(node);
297 static void iq_clicked_cb(GtkWidget *w, gpointer nul)
299 GtkWidget *vbox, *hbox, *to_entry, *label, *type_combo;
300 GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
301 char *to;
302 int result;
303 char *stanza;
305 GtkWidget *dialog = gtk_dialog_new_with_buttons("<iq/>",
306 GTK_WINDOW(console->window),
307 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
308 GTK_STOCK_CANCEL,
309 GTK_RESPONSE_REJECT,
310 GTK_STOCK_OK,
311 GTK_RESPONSE_ACCEPT,
312 NULL);
313 gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
314 gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
315 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
317 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
318 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
320 label = gtk_label_new("To:");
321 gtk_label_set_xalign(GTK_LABEL(label), 0);
322 gtk_size_group_add_widget(sg, label);
323 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
325 to_entry = gtk_entry_new();
326 gtk_entry_set_activates_default (GTK_ENTRY (to_entry), TRUE);
327 gtk_box_pack_start(GTK_BOX(hbox), to_entry, FALSE, FALSE, 0);
329 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
330 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
331 label = gtk_label_new("Type:");
332 gtk_label_set_xalign(GTK_LABEL(label), 0);
334 gtk_size_group_add_widget(sg, label);
335 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
336 type_combo = gtk_combo_box_text_new();
337 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "get");
338 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "set");
339 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "result");
340 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "error");
341 gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo), 0);
342 gtk_box_pack_start(GTK_BOX(hbox), type_combo, FALSE, FALSE, 0);
344 gtk_widget_show_all(vbox);
346 result = gtk_dialog_run(GTK_DIALOG(dialog));
347 if (result != GTK_RESPONSE_ACCEPT) {
348 gtk_widget_destroy(dialog);
349 return;
352 to = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(to_entry)), -1);
354 stanza = g_strdup_printf("&lt;iq %s%s%s id='console%x' type='%s'&gt;"
355 "<a id=caret></a>"
356 "&lt;/iq&gt;",
357 to && *to ? "to='" : "",
358 to && *to ? to : "",
359 to && *to ? "'" : "",
360 g_random_int(),
361 gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(type_combo)));
363 pidgin_webview_load_html_string_with_selection(PIDGIN_WEBVIEW(console->entry), stanza);
364 gtk_widget_grab_focus(console->entry);
365 g_free(to);
366 g_free(stanza);
368 gtk_widget_destroy(dialog);
369 g_object_unref(sg);
372 static void presence_clicked_cb(GtkWidget *w, gpointer nul)
374 GtkWidget *vbox;
375 GtkWidget *hbox;
376 GtkWidget *to_entry;
377 GtkWidget *status_entry;
378 GtkWidget *priority_entry;
379 GtkWidget *label;
380 GtkWidget *show_combo;
381 GtkWidget *type_combo;
382 GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
383 char *to, *status, *priority;
384 const char *type, *show;
385 int result;
386 char *stanza;
388 GtkWidget *dialog = gtk_dialog_new_with_buttons("<presence/>",
389 GTK_WINDOW(console->window),
390 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
391 GTK_STOCK_CANCEL,
392 GTK_RESPONSE_REJECT,
393 GTK_STOCK_OK,
394 GTK_RESPONSE_ACCEPT,
395 NULL);
396 gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
397 gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
398 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
400 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
401 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
403 label = gtk_label_new("To:");
404 gtk_size_group_add_widget(sg, label);
405 gtk_label_set_xalign(GTK_LABEL(label), 0);
406 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
408 to_entry = gtk_entry_new();
409 gtk_entry_set_activates_default (GTK_ENTRY (to_entry), TRUE);
410 gtk_box_pack_start(GTK_BOX(hbox), to_entry, FALSE, FALSE, 0);
412 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
413 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
414 label = gtk_label_new("Type:");
415 gtk_label_set_xalign(GTK_LABEL(label), 0);
416 gtk_size_group_add_widget(sg, label);
417 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
418 type_combo = gtk_combo_box_text_new();
419 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "default");
420 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "unavailable");
421 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "subscribe");
422 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "unsubscribe");
423 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "subscribed");
424 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "unsubscribed");
425 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "probe");
426 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "error");
427 gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo), 0);
428 gtk_box_pack_start(GTK_BOX(hbox), type_combo, FALSE, FALSE, 0);
430 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
431 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
432 label = gtk_label_new("Show:");
433 gtk_label_set_xalign(GTK_LABEL(label), 0);
434 gtk_size_group_add_widget(sg, label);
435 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
436 show_combo = gtk_combo_box_text_new();
437 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(show_combo), "default");
438 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(show_combo), "away");
439 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(show_combo), "dnd");
440 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(show_combo), "xa");
441 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(show_combo), "chat");
443 gtk_combo_box_set_active(GTK_COMBO_BOX(show_combo), 0);
444 gtk_box_pack_start(GTK_BOX(hbox), show_combo, FALSE, FALSE, 0);
446 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
447 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
449 label = gtk_label_new("Status:");
450 gtk_label_set_xalign(GTK_LABEL(label), 0);
451 gtk_size_group_add_widget(sg, label);
452 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
454 status_entry = gtk_entry_new();
455 gtk_entry_set_activates_default (GTK_ENTRY (status_entry), TRUE);
456 gtk_box_pack_start(GTK_BOX(hbox), status_entry, FALSE, FALSE, 0);
458 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
459 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
461 label = gtk_label_new("Priority:");
462 gtk_label_set_xalign(GTK_LABEL(label), 0);
463 gtk_size_group_add_widget(sg, label);
464 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
466 priority_entry = gtk_spin_button_new_with_range(-128, 127, 1);
467 gtk_spin_button_set_value(GTK_SPIN_BUTTON(priority_entry), 0);
468 gtk_box_pack_start(GTK_BOX(hbox), priority_entry, FALSE, FALSE, 0);
470 gtk_widget_show_all(vbox);
472 result = gtk_dialog_run(GTK_DIALOG(dialog));
473 if (result != GTK_RESPONSE_ACCEPT) {
474 gtk_widget_destroy(dialog);
475 return;
478 to = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(to_entry)), -1);
479 type = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(type_combo));
480 if (!strcmp(type, "default"))
481 type = "";
482 show = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(show_combo));
483 if (!strcmp(show, "default"))
484 show = "";
485 status = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(status_entry)), -1);
486 priority = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(priority_entry)), -1);
487 if (!strcmp(priority, "0"))
488 *priority = '\0';
490 stanza = g_strdup_printf("&lt;presence %s%s%s id='console%x' %s%s%s&gt;"
491 "%s%s%s%s%s%s%s%s%s"
492 "<a id=caret></a>"
493 "&lt;/presence&gt;",
494 *to ? "to='" : "",
495 *to ? to : "",
496 *to ? "'" : "",
497 g_random_int(),
499 *type ? "type='" : "",
500 *type ? type : "",
501 *type ? "'" : "",
503 *show ? "&lt;show&gt;" : "",
504 *show ? show : "",
505 *show ? "&lt;/show&gt;" : "",
507 *status ? "&lt;status&gt;" : "",
508 *status ? status : "",
509 *status ? "&lt;/status&gt;" : "",
511 *priority ? "&lt;priority&gt;" : "",
512 *priority ? priority : "",
513 *priority ? "&lt;/priority&gt;" : "");
515 pidgin_webview_load_html_string_with_selection(PIDGIN_WEBVIEW(console->entry), stanza);
516 gtk_widget_grab_focus(console->entry);
517 g_free(stanza);
518 g_free(to);
519 g_free(status);
520 g_free(priority);
522 gtk_widget_destroy(dialog);
523 g_object_unref(sg);
526 static void message_clicked_cb(GtkWidget *w, gpointer nul)
528 GtkWidget *vbox;
529 GtkWidget *hbox;
530 GtkWidget *to_entry;
531 GtkWidget *body_entry;
532 GtkWidget *thread_entry;
533 GtkWidget *subject_entry;
534 GtkWidget *label;
535 GtkWidget *type_combo;
536 GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
537 char *to, *body, *thread, *subject;
538 char *stanza;
539 int result;
541 GtkWidget *dialog = gtk_dialog_new_with_buttons("<message/>",
542 GTK_WINDOW(console->window),
543 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
544 GTK_STOCK_CANCEL,
545 GTK_RESPONSE_REJECT,
546 GTK_STOCK_OK,
547 GTK_RESPONSE_ACCEPT,
548 NULL);
549 gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
550 gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
551 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
553 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
554 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
556 label = gtk_label_new("To:");
557 gtk_label_set_xalign(GTK_LABEL(label), 0);
558 gtk_size_group_add_widget(sg, label);
559 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
561 to_entry = gtk_entry_new();
562 gtk_entry_set_activates_default (GTK_ENTRY (to_entry), TRUE);
563 gtk_box_pack_start(GTK_BOX(hbox), to_entry, FALSE, FALSE, 0);
565 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
566 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
567 label = gtk_label_new("Type:");
568 gtk_label_set_xalign(GTK_LABEL(label), 0);
569 gtk_size_group_add_widget(sg, label);
570 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
571 type_combo = gtk_combo_box_text_new();
572 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "chat");
573 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "headline");
574 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "groupchat");
575 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "normal");
576 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type_combo), "error");
577 gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo), 0);
578 gtk_box_pack_start(GTK_BOX(hbox), type_combo, FALSE, FALSE, 0);
580 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
581 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
583 label = gtk_label_new("Body:");
584 gtk_label_set_xalign(GTK_LABEL(label), 0);
585 gtk_size_group_add_widget(sg, label);
586 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
588 body_entry = gtk_entry_new();
589 gtk_entry_set_activates_default (GTK_ENTRY (body_entry), TRUE);
590 gtk_box_pack_start(GTK_BOX(hbox), body_entry, FALSE, FALSE, 0);
592 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
593 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
595 label = gtk_label_new("Subject:");
596 gtk_label_set_xalign(GTK_LABEL(label), 0);
597 gtk_size_group_add_widget(sg, label);
598 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
600 subject_entry = gtk_entry_new();
601 gtk_entry_set_activates_default (GTK_ENTRY (subject_entry), TRUE);
602 gtk_box_pack_start(GTK_BOX(hbox), subject_entry, FALSE, FALSE, 0);
604 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
605 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
607 label = gtk_label_new("Thread:");
608 gtk_label_set_xalign(GTK_LABEL(label), 0);
609 gtk_size_group_add_widget(sg, label);
610 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
612 thread_entry = gtk_entry_new();
613 gtk_entry_set_activates_default (GTK_ENTRY (thread_entry), TRUE);
614 gtk_box_pack_start(GTK_BOX(hbox), thread_entry, FALSE, FALSE, 0);
616 gtk_widget_show_all(vbox);
618 result = gtk_dialog_run(GTK_DIALOG(dialog));
619 if (result != GTK_RESPONSE_ACCEPT) {
620 gtk_widget_destroy(dialog);
621 return;
624 to = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(to_entry)), -1);
625 body = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(body_entry)), -1);
626 thread = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(thread_entry)), -1);
627 subject = g_markup_escape_text(gtk_entry_get_text(GTK_ENTRY(subject_entry)), -1);
629 stanza = g_strdup_printf("&lt;message %s%s%s id='console%x' type='%s'&gt;"
630 "%s%s%s%s%s%s%s%s%s"
631 "<a id=caret></a>"
632 "&lt;/message&gt;",
634 *to ? "to='" : "",
635 *to ? to : "",
636 *to ? "'" : "",
637 g_random_int(),
638 gtk_combo_box_text_get_active_text(
639 GTK_COMBO_BOX_TEXT(type_combo)),
641 *body ? "&lt;body&gt;" : "",
642 *body ? body : "",
643 *body ? "&lt;/body&gt;" : "",
645 *subject ? "&lt;subject&gt;" : "",
646 *subject ? subject : "",
647 *subject ? "&lt;/subject&gt;" : "",
649 *thread ? "&lt;thread&gt;" : "",
650 *thread ? thread : "",
651 *thread ? "&lt;/thread&gt;" : "");
653 pidgin_webview_load_html_string_with_selection(PIDGIN_WEBVIEW(console->entry), stanza);
654 gtk_widget_grab_focus(console->entry);
655 g_free(stanza);
656 g_free(to);
657 g_free(body);
658 g_free(thread);
659 g_free(subject);
661 gtk_widget_destroy(dialog);
662 g_object_unref(sg);
665 static void
666 signing_on_cb(PurpleConnection *gc)
668 PurpleAccount *account;
670 if (!console)
671 return;
673 account = purple_connection_get_account(gc);
674 if (!xmppconsole_is_xmpp_account(account))
675 return;
677 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(console->dropdown),
678 purple_account_get_username(account));
679 console->accounts = g_list_append(console->accounts, gc);
680 console->count++;
682 if (console->count == 1) {
683 console->gc = gc;
684 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(console->webview), EMPTY_HTML);
685 gtk_combo_box_set_active(GTK_COMBO_BOX(console->dropdown), 0);
686 } else
687 gtk_widget_show_all(console->hbox);
690 static void
691 signed_off_cb(PurpleConnection *gc)
693 int i = 0;
694 GList *l;
696 if (!console)
697 return;
699 l = console->accounts;
700 while (l) {
701 PurpleConnection *g = l->data;
702 if (gc == g)
703 break;
704 i++;
705 l = l->next;
708 if (l == NULL)
709 return;
711 gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(console->dropdown), i);
712 console->accounts = g_list_remove(console->accounts, gc);
713 console->count--;
715 if (gc == console->gc) {
716 char *tmp = g_strdup_printf("<div class=info>%s</div>",
717 _("Logged out."));
718 pidgin_webview_append_html(PIDGIN_WEBVIEW(console->webview), tmp);
719 g_free(tmp);
720 console->gc = NULL;
724 static void
725 console_destroy(GtkWidget *window, gpointer nul)
727 g_list_free(console->accounts);
728 g_free(console);
729 console = NULL;
732 static void
733 dropdown_changed_cb(GtkComboBox *widget, gpointer nul)
735 if (!console)
736 return;
738 console->gc = g_list_nth_data(console->accounts, gtk_combo_box_get_active(GTK_COMBO_BOX(console->dropdown)));
739 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(console->webview), EMPTY_HTML);
742 static void
743 create_console(PurplePluginAction *action)
745 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
746 GtkWidget *label;
747 GtkWidget *toolbar;
748 GList *connections;
749 GtkToolItem *button;
751 if (console) {
752 gtk_window_present(GTK_WINDOW(console->window));
753 return;
756 console = g_new0(XmppConsole, 1);
758 console->window = pidgin_create_window(_("XMPP Console"), PIDGIN_HIG_BORDER, NULL, TRUE);
759 g_signal_connect(G_OBJECT(console->window), "destroy", G_CALLBACK(console_destroy), NULL);
760 gtk_window_set_default_size(GTK_WINDOW(console->window), 580, 400);
761 gtk_container_add(GTK_CONTAINER(console->window), vbox);
763 console->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
764 gtk_box_pack_start(GTK_BOX(vbox), console->hbox, FALSE, FALSE, 0);
765 label = gtk_label_new(_("Account: "));
766 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
767 gtk_box_pack_start(GTK_BOX(console->hbox), label, FALSE, FALSE, 0);
768 console->dropdown = gtk_combo_box_text_new();
769 for (connections = purple_connections_get_all(); connections; connections = connections->next) {
770 PurpleConnection *gc = connections->data;
771 if (xmppconsole_is_xmpp_account(purple_connection_get_account(gc))) {
772 console->count++;
773 console->accounts = g_list_append(console->accounts, gc);
774 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(console->dropdown),
775 purple_account_get_username(purple_connection_get_account(gc)));
776 if (!console->gc)
777 console->gc = gc;
780 gtk_combo_box_set_active(GTK_COMBO_BOX(console->dropdown), 0);
781 gtk_box_pack_start(GTK_BOX(console->hbox), console->dropdown, TRUE, TRUE, 0);
782 g_signal_connect(G_OBJECT(console->dropdown), "changed", G_CALLBACK(dropdown_changed_cb), NULL);
784 console->webview = pidgin_webview_new(FALSE);
785 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(console->webview), EMPTY_HTML);
786 if (console->count == 0) {
787 char *tmp = g_strdup_printf("<div class=info>%s</div>",
788 _("Not connected to XMPP"));
789 pidgin_webview_append_html(PIDGIN_WEBVIEW(console->webview), tmp);
790 g_free(tmp);
792 gtk_box_pack_start(GTK_BOX(vbox),
793 pidgin_make_scrollable(console->webview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_ETCHED_IN, -1, -1),
794 TRUE, TRUE, 0);
796 toolbar = gtk_toolbar_new();
797 button = gtk_tool_button_new(NULL, "<iq/>");
798 gtk_tool_item_set_is_important(button, TRUE);
799 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(iq_clicked_cb), NULL);
800 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
802 button = gtk_tool_button_new(NULL, "<presence/>");
803 gtk_tool_item_set_is_important(button, TRUE);
804 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(presence_clicked_cb), NULL);
805 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
807 button = gtk_tool_button_new(NULL, "<message/>");
808 gtk_tool_item_set_is_important(button, TRUE);
809 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(message_clicked_cb), NULL);
810 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
812 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
814 console->entry = pidgin_webview_new(TRUE);
815 pidgin_webview_set_whole_buffer_formatting_only(PIDGIN_WEBVIEW(console->entry), TRUE);
816 g_signal_connect(G_OBJECT(console->entry),"key-press-event", G_CALLBACK(message_send_cb), console);
818 console->sw = pidgin_make_scrollable(console->entry, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_ETCHED_IN, -1, -1);
819 gtk_box_pack_start(GTK_BOX(vbox), console->sw, FALSE, FALSE, 0);
820 g_signal_connect(G_OBJECT(console->entry), "changed", G_CALLBACK(entry_changed_cb), NULL);
822 entry_changed_cb(console->entry, NULL);
824 gtk_widget_show_all(console->window);
825 if (console->count < 2)
826 gtk_widget_hide(console->hbox);
829 static GList *
830 actions(PurplePlugin *plugin)
832 GList *l = NULL;
833 PurplePluginAction *act = NULL;
835 act = purple_plugin_action_new(_("XMPP Console"), create_console);
836 l = g_list_append(l, act);
838 return l;
841 static PidginPluginInfo *
842 plugin_query(GError **error)
844 const gchar * const authors[] = {
845 "Sean Egan <seanegan@gmail.com>",
846 NULL
849 return pidgin_plugin_info_new(
850 "id", PLUGIN_ID,
851 "name", N_("XMPP Console"),
852 "version", DISPLAY_VERSION,
853 "category", N_("Protocol utility"),
854 "summary", N_("Send and receive raw XMPP stanzas."),
855 "description", N_("This plugin is useful for debugging XMPP servers "
856 "or clients."),
857 "authors", authors,
858 "website", PURPLE_WEBSITE,
859 "abi-version", PURPLE_ABI_VERSION,
860 "actions-cb", actions,
861 NULL
865 static gboolean
866 plugin_load(PurplePlugin *plugin, GError **error)
868 int i;
869 gboolean any_registered = FALSE;
871 xmpp_console_handle = plugin;
873 i = 0;
874 while (xmpp_prpls[i] != NULL) {
875 PurpleProtocol *xmpp;
877 xmpp = purple_protocols_find(xmpp_prpls[i]);
878 i++;
880 if (!xmpp)
881 continue;
882 any_registered = TRUE;
884 purple_signal_connect(xmpp, "jabber-receiving-xmlnode",
885 xmpp_console_handle,
886 PURPLE_CALLBACK(purple_xmlnode_received_cb), NULL);
887 purple_signal_connect(xmpp, "jabber-sending-text",
888 xmpp_console_handle,
889 PURPLE_CALLBACK(purple_xmlnode_sent_cb), NULL);
892 if (!any_registered) {
893 g_set_error(error, PLUGIN_DOMAIN, 0, _("No XMPP protocol is loaded."));
894 return FALSE;
897 purple_signal_connect(purple_connections_get_handle(), "signing-on",
898 plugin, PURPLE_CALLBACK(signing_on_cb), NULL);
899 purple_signal_connect(purple_connections_get_handle(), "signed-off",
900 plugin, PURPLE_CALLBACK(signed_off_cb), NULL);
902 return TRUE;
905 static gboolean
906 plugin_unload(PurplePlugin *plugin, GError **error)
908 if (console)
909 gtk_widget_destroy(console->window);
910 return TRUE;
913 PURPLE_PLUGIN_INIT(xmppconsole, plugin_query, plugin_load, plugin_unload);