3 * Bacula Gnome Tray Monitor
5 * Nicolas Boichat, August MMIV
11 Copyright (C) 2004 Kern Sibbald and John Walker
13 This library is free software; you can redistribute it and/or
14 modify it under the terms of the GNU Lesser General Public
15 License as published by the Free Software Foundation; either
16 version 2.1 of the License, or (at your option) any later version.
18 This library is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 Lesser General Public License for more details.
23 You should have received a copy of the GNU Lesser General Public
24 License along with this library; if not, write to the Free
25 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
31 #include "tray-monitor.h"
33 #include "eggstatusicon.h"
37 #include "running.xpm"
38 //#include "saving.xpm"
41 /* Imported functions */
42 int authenticate_file_daemon(JCR *jcr, MONITOR *monitor, CLIENT* client);
43 int authenticate_storage_daemon(JCR *jcr, MONITOR *monitor, STORE* store);
45 /* Forward referenced functions */
46 void writecmd(monitoritem* item, const char* command);
47 int docmd(monitoritem* item, const char* command, GSList** list);
48 stateenum getstatus(monitoritem* item, int current, GString** str);
50 /* Static variables */
51 static char *configfile = NULL;
52 static MONITOR *monitor;
55 static int nitems = 0;
56 static int fullitem = -1; //Item to be display in detailled status window
57 static int lastupdated = -1; //Last item updated
58 static monitoritem items[32];
60 /* Data received from FD/SD */
61 static char OKqstatus[] = "2000 OK .status\n";
62 static char DotStatusJob[] = "JobId=%d JobStatus=%c JobErrors=%d\n";
64 /* UI variables and functions */
66 stateenum currentstatus = warn;
68 static gboolean fd_read(gpointer data);
69 void trayMessage(const char *fmt,...);
70 void changeStatus(monitoritem* item, stateenum status);
71 void changeStatusMessage(monitoritem* item, const char *fmt,...);
74 static void TrayIconActivate(GtkWidget *widget, gpointer data);
75 static void TrayIconExit(unsigned int activateTime, unsigned int button);
76 static void TrayIconPopupMenu(unsigned int button, unsigned int activateTime);
77 static void MonitorAbout(GtkWidget *widget, gpointer data);
78 static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data);
81 static EggStatusIcon *mTrayIcon;
82 static GtkWidget *mTrayMenu;
83 static GtkWidget *window;
84 static GtkWidget *textview;
85 static GtkTextBuffer *buffer;
87 #define CONFIG_FILE "./tray-monitor.conf" /* default configuration file */
92 "Copyright (C) 2000-2004 Kern Sibbald and John Walker\n"
93 "Written by Nicolas Boichat (2004)\n"
94 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
95 "Usage: tray-monitor [-c config_file] [-d debug_level]\n"
96 " -c <file> set configuration file to file\n"
97 " -dnn set debug level to nn\n"
98 " -t test - read configuration and exit\n"
99 " -? print this message.\n"
100 "\n"), HOST_OS, DISTNAME, DISTVER);
103 static GtkWidget *new_image_button(const gchar *stock_id,
104 const gchar *label_text) {
110 button = gtk_button_new();
112 box = gtk_hbox_new(FALSE, 0);
113 gtk_container_set_border_width(GTK_CONTAINER(box), 2);
114 image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
115 label = gtk_label_new(label_text);
117 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 3);
118 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 3);
120 gtk_widget_show(image);
121 gtk_widget_show(label);
123 gtk_widget_show(box);
125 gtk_container_add(GTK_CONTAINER(button), box);
130 /*********************************************************************
132 * Main Bacula Tray Monitor -- User Interface Program
135 int main(int argc, char *argv[])
138 bool test_config = false;
143 my_name_is(argc, argv, "tray-monitor");
144 textdomain("bacula");
145 init_msg(NULL, NULL);
146 working_directory = "/tmp";
147 args = get_pool_memory(PM_FNAME);
149 while ((ch = getopt(argc, argv, "bc:d:th?f:s:")) != -1) {
151 case 'c': /* configuration file */
152 if (configfile != NULL) {
155 configfile = bstrdup(optarg);
159 debug_level = atoi(optarg);
160 if (debug_level <= 0) {
184 if (configfile == NULL) {
185 configfile = bstrdup(CONFIG_FILE);
188 parse_config(configfile);
192 foreach_res(filed, R_CLIENT) {
193 items[nitems].type = R_CLIENT;
194 items[nitems].resource = filed;
195 items[nitems].D_sock = NULL;
196 items[nitems].state = warn;
199 foreach_res(stored, R_STORAGE) {
200 items[nitems].type = R_STORAGE;
201 items[nitems].resource = stored;
202 items[nitems].D_sock = NULL;
203 items[nitems].state = warn;
209 Emsg1(M_ERROR_TERM, 0, _("No Client nor Storage resource defined in %s\n\
210 Without that I don't how to get status from the File or Storage Daemon :-(\n"), configfile);
217 (void)WSA_Init(); /* Initialize Windows sockets */
220 monitor = (MONITOR*)GetNextRes(R_MONITOR, (RES *)NULL);
223 gtk_init (&argc, &argv);
225 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
226 // This should be ideally replaced by a completely libpr0n-based icon rendering.
227 mTrayIcon = egg_status_icon_new_from_pixbuf(pixbuf);
228 g_signal_connect(G_OBJECT(mTrayIcon), "activate", G_CALLBACK(TrayIconActivate), NULL);
229 g_signal_connect(G_OBJECT(mTrayIcon), "popup-menu", G_CALLBACK(TrayIconPopupMenu), NULL);
230 g_object_unref(G_OBJECT(pixbuf));
232 mTrayMenu = gtk_menu_new();
236 entry = gtk_menu_item_new_with_label("Open status window...");
237 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconActivate), NULL);
238 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
240 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
242 entry = gtk_menu_item_new_with_label("Exit");
243 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconExit), NULL);
244 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
246 gtk_widget_show_all(mTrayMenu);
248 timerTag = g_timeout_add( 5000/nitems, fd_read, NULL );
250 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
252 gtk_window_set_title(GTK_WINDOW(window), "Bacula tray monitor");
254 g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL);
255 //g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
257 gtk_container_set_border_width(GTK_CONTAINER(window), 10);
259 GtkWidget* vbox = gtk_vbox_new(FALSE, 10);
261 /*textview = gtk_text_view_new();
263 buffer = gtk_text_buffer_new(NULL);
265 gtk_text_buffer_set_text(buffer, "", -1);
267 PangoFontDescription *font_desc = pango_font_description_from_string ("Fixed 10");
268 gtk_widget_modify_font(textview, font_desc);
269 pango_font_description_free(font_desc);
271 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 20);
272 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 20);
274 gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
276 gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
278 gtk_box_pack_start(GTK_BOX(vbox), textview, TRUE, FALSE, 0);*/
280 GtkWidget* daemon_table = gtk_table_new((nitems*2)+1, 3, FALSE);
282 gtk_table_set_col_spacings(GTK_TABLE(daemon_table), 8);
284 GtkWidget* separator = gtk_hseparator_new();
285 gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, 0, 1);
288 for (int i = 0; i < nitems; i++) {
289 switch (items[i].type) {
291 str = g_string_new(((CLIENT*)(items[i].resource))->hdr.name);
292 g_string_append(str, _(" (FD)"));
295 str = g_string_new(((STORE*)(items[i].resource))->hdr.name);
296 g_string_append(str, _(" (SD)"));
302 GtkWidget* label = gtk_label_new(str->str);
303 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
304 items[i].image = gtk_image_new_from_pixbuf(pixbuf);
306 items[i].label = gtk_label_new(_("Unknown status."));
308 gtk_table_attach_defaults(GTK_TABLE(daemon_table), label, 0, 1, (i*2)+1, (i*2)+2);
309 gtk_table_attach_defaults(GTK_TABLE(daemon_table), items[i].image, 1, 2, (i*2)+1, (i*2)+2);
310 gtk_table_attach_defaults(GTK_TABLE(daemon_table), items[i].label, 2, 3, (i*2)+1, (i*2)+2);
312 GtkWidget* separator = gtk_hseparator_new();
313 gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, (i*2)+2, (i*2)+3);
316 gtk_box_pack_start(GTK_BOX(vbox), daemon_table, TRUE, FALSE, 0);
318 GtkWidget* hbox = gtk_hbox_new(FALSE, 10);
320 GtkWidget* button = new_image_button("gtk-help", "About");
321 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorAbout), NULL);
323 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
325 button = new_image_button("gtk-close", "Close");
326 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_hide), G_OBJECT(window));
328 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
330 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
332 gtk_container_add(GTK_CONTAINER (window), vbox);
334 gtk_widget_show_all(vbox);
338 for (i = 0; i < nitems; i++) {
339 if (items[i].D_sock) {
340 writecmd(&items[i], "quit");
341 bnet_sig(items[i].D_sock, BNET_TERMINATE); /* send EOF */
342 bnet_close(items[i].D_sock);
346 free_pool_memory(args);
347 (void)WSACleanup(); /* Cleanup Windows sockets */
351 static void MonitorAbout(GtkWidget *widget, gpointer data) {
353 GtkWidget* about = gtk_message_dialog_new_with_markup(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
354 "<span size='x-large' weight='bold'>Bacula Tray Monitor</span>\n\n"
355 "Copyright (C) 2004 Kern Sibbald and John Walker\n"
356 "Written by Nicolas Boichat\n"
357 "\n<small>Version: " VERSION " (" BDATE ") %s %s %s</small>"
358 ), HOST_OS, DISTNAME, DISTVER);
360 GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
361 "Bacula Tray Monitor\n\n"
362 "Copyright (C) 2004 Kern Sibbald and John Walker\n"
363 "Written by Nicolas Boichat\n"
364 "\nVersion: " VERSION " (" BDATE ") %s %s %s"
365 ), HOST_OS, DISTNAME, DISTVER);
367 gtk_dialog_run(GTK_DIALOG(about));
368 gtk_widget_destroy(about);
371 static gboolean delete_event( GtkWidget *widget,
374 gtk_widget_hide(window);
375 return TRUE; /* do not destroy the window */
378 static void TrayIconActivate(GtkWidget *widget, gpointer data) {
379 gtk_widget_show(window);
382 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) {
383 gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, 1, 0);
384 gtk_widget_show_all(mTrayMenu);
387 static void TrayIconExit(unsigned int activateTime, unsigned int button) {
391 static int authenticate_daemon(monitoritem* item, JCR *jcr) {
392 switch (item->type) {
394 return authenticate_file_daemon(jcr, monitor, (CLIENT*)item->resource);
397 return authenticate_storage_daemon(jcr, monitor, (STORE*)item->resource);
400 printf("Error, currentitem is not a Client or a Storage..\n");
406 static gboolean fd_read(gpointer data) {
407 GtkTextBuffer *newbuffer = gtk_text_buffer_new(NULL);
408 GtkTextIter start, stop, nstart, nstop;
411 stateenum stat = idle, nstat;
413 GString *strlast, *strcurrent;
416 if (lastupdated == nitems) {
420 nstat = getstatus(&items[lastupdated], 1, &strcurrent);
421 if (nstat > stat) stat = nstat;
422 nstat = getstatus(&items[lastupdated], 0, &strlast);
423 if (nstat > stat) stat = nstat;
425 changeStatusMessage(&items[lastupdated], "Current job: %s\nLast job: %s", strcurrent->str, strlast->str);
426 changeStatus(&items[lastupdated], stat);
428 g_string_free(strcurrent, TRUE);
429 g_string_free(strlast, TRUE);
431 if (lastupdated == fullitem) {
432 docmd(&items[lastupdated], "status", &list);
436 gtk_text_buffer_get_end_iter(newbuffer, &stop);
437 gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
438 if (it->data) g_string_free((GString*)it->data, TRUE);
439 } while ((it = it->next) != NULL);
441 /* Keep the selection if necessary */
442 if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
443 gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
444 gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop, gtk_text_iter_get_offset(&stop ));
447 gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
449 gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
450 gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
454 g_object_unref(buffer);
457 gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
463 stateenum getstatus(monitoritem* item, int current, GString** str) {
465 stateenum ret = error;
466 int jobid = 0, joberrors = 0;
467 char jobstatus = JS_ErrorTerminated;
470 *str = g_string_sized_new(128);
473 docmd(&items[lastupdated], ".status current", &list);
476 docmd(&items[lastupdated], ".status last", &list);
480 if ((it == NULL) || (strcmp(((GString*)it->data)->str, OKqstatus) != 0)) {
481 g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
484 else if ((it = it->next) == NULL) {
486 g_string_append(*str, _("No current job."));
489 g_string_append(*str, _("No last job."));
493 else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
496 ret = (joberrors > 0) ? warn : running;
497 g_string_append_printf(*str, _("Job status: Created (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
500 ret = (joberrors > 0) ? warn : running;
501 g_string_append_printf(*str, _("Job status: Running (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
504 ret = (joberrors > 0) ? warn : running;
505 g_string_append_printf(*str, _("Job status: Error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
508 g_string_append_printf(*str, _("Job status: Terminated (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
509 ret = (joberrors > 0) ? warn : idle;
511 case JS_ErrorTerminated:
512 g_string_append_printf(*str, _("Job status: Terminated in error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
516 g_string_append_printf(*str, _("Job status: Fatal error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
520 g_string_append_printf(*str, _("Job status: Blocked (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
524 g_string_append_printf(*str, _("Job status: Canceled (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
528 g_string_append_printf(*str, _("Job status: Unknown(%c) (%d error%s)"), jobstatus, joberrors, (joberrors > 1) ? "s" : "");
534 fprintf(stderr, "Bad scan : '%s' %d\n", (it == NULL) ? "" : ((GString*)it->data)->str, k);
540 if (it->data) g_string_free((GString*)it->data, TRUE);
541 } while ((it = it->next) != NULL);
548 int docmd(monitoritem* item, const char* command, GSList** list) {
552 *list = g_slist_alloc();
554 //str = g_string_sized_new(64);
557 memset(&jcr, 0, sizeof(jcr));
562 switch (item->type) {
564 filed = (CLIENT*)item->resource;
565 trayMessage("Connecting to Client %s:%d\n", filed->address, filed->FDport);
566 changeStatusMessage(item, "Connecting to Client %s:%d", filed->address, filed->FDport);
567 item->D_sock = bnet_connect(NULL, 0, 0, "File daemon", filed->address, NULL, filed->FDport, 0);
568 jcr.file_bsock = item->D_sock;
571 stored = (STORE*)item->resource;
572 trayMessage("Connecting to Storage %s:%d\n", stored->address, stored->SDport);
573 changeStatusMessage(item, "Connecting to Storage %s:%d", stored->address, stored->SDport);
574 item->D_sock = bnet_connect(NULL, 0, 0, "Storage daemon", stored->address, NULL, stored->SDport, 0);
575 jcr.store_bsock = item->D_sock;
578 printf("Error, currentitem is not a Client or a Storage..\n");
583 if (item->D_sock == NULL) {
584 g_slist_append(*list, g_string_new("Cannot connect to daemon.\n"));
585 changeStatusMessage(item, "Cannot connect to daemon.");
586 changeStatus(NULL, error);
590 if (!authenticate_daemon(item, &jcr)) {
591 str = g_string_sized_new(64);
592 g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
593 g_slist_append(*list, str);
594 changeStatus(NULL, error);
595 changeStatusMessage(item, "Authentication error : %s", item->D_sock->msg);
600 trayMessage("Opened connection with File daemon.\n");
601 changeStatusMessage(item, "Opened connection with File daemon.");
604 writecmd(item, command);
607 if ((stat = bnet_recv(item->D_sock)) >= 0) {
608 g_slist_append(*list, g_string_new(item->D_sock->msg));
610 else if (stat == BNET_SIGNAL) {
611 if (item->D_sock->msglen == BNET_EOD) {
614 else if (item->D_sock->msglen == BNET_HEARTBEAT) {
615 bnet_sig(item->D_sock, BNET_HB_RESPONSE);
616 g_slist_append(*list, g_string_new("<< Heartbeat signal received, answered. >>\n"));
619 str = g_string_sized_new(64);
620 g_string_printf(str, "<< Unexpected signal received : %s >>\n", bnet_sig_to_ascii(item->D_sock));
621 g_slist_append(*list, str);
624 else { /* BNET_HARDEOF || BNET_ERROR */
625 g_slist_append(*list, g_string_new("<ERROR>\n"));
627 changeStatus(NULL, error);
628 changeStatusMessage(item, "Error : BNET_HARDEOF or BNET_ERROR");
632 if (is_bnet_stop(item->D_sock)) {
633 g_string_append_printf(str, "<STOP>\n");
635 changeStatus(NULL, error);
636 changeStatusMessage(item, "Error : Connection closed.");
637 return 0; /* error or term */
642 void writecmd(monitoritem* item, const char* command) {
644 item->D_sock->msglen = strlen(command);
645 pm_strcpy(&item->D_sock->msg, command);
646 bnet_send(item->D_sock);
650 /* Note: Does not seem to work either on Gnome nor KDE... */
651 void trayMessage(const char *fmt,...) {
655 va_start(arg_ptr, fmt);
656 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
659 fprintf(stderr, buf);
661 egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
664 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
668 va_start(arg_ptr, fmt);
669 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
672 gtk_label_set_text(GTK_LABEL(item->label), buf);
675 void changeStatus(monitoritem* item, stateenum status) {
677 if (status == currentstatus)
681 if (status == item->state)
689 xpm = (const char**)&xpm_error;
692 xpm = (const char**)&xpm_idle;
695 xpm = (const char**)&xpm_running;
698 xpm = (const char**)&xpm_saving;
701 xpm = (const char**)&xpm_warn;
708 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
710 egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
711 gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
712 currentstatus = status;
715 gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
716 item->state = status;