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 TrayIconDaemonChanged(GtkWidget *widget, monitoritem* data);
75 static void TrayIconActivate(GtkWidget *widget, gpointer data);
76 static void TrayIconExit(unsigned int activateTime, unsigned int button);
77 static void TrayIconPopupMenu(unsigned int button, unsigned int activateTime);
78 static void MonitorAbout(GtkWidget *widget, gpointer data);
79 static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data);
82 static EggStatusIcon *mTrayIcon;
83 static GtkWidget *mTrayMenu;
84 static GtkWidget *window;
85 static GtkWidget *textview;
86 static GtkTextBuffer *buffer;
88 #define CONFIG_FILE "./tray-monitor.conf" /* default configuration file */
93 "Copyright (C) 2000-2004 Kern Sibbald and John Walker\n"
94 "Written by Nicolas Boichat (2004)\n"
95 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
96 "Usage: tray-monitor [-c config_file] [-d debug_level]\n"
97 " -c <file> set configuration file to file\n"
98 " -dnn set debug level to nn\n"
99 " -t test - read configuration and exit\n"
100 " -? print this message.\n"
101 "\n"), HOST_OS, DISTNAME, DISTVER);
104 static GtkWidget *new_image_button(const gchar *stock_id,
105 const gchar *label_text) {
111 button = gtk_button_new();
113 box = gtk_hbox_new(FALSE, 0);
114 gtk_container_set_border_width(GTK_CONTAINER(box), 2);
115 image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
116 label = gtk_label_new(label_text);
118 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 3);
119 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 3);
121 gtk_widget_show(image);
122 gtk_widget_show(label);
124 gtk_widget_show(box);
126 gtk_container_add(GTK_CONTAINER(button), box);
131 /*********************************************************************
133 * Main Bacula Tray Monitor -- User Interface Program
136 int main(int argc, char *argv[])
139 bool test_config = false;
144 my_name_is(argc, argv, "tray-monitor");
145 textdomain("bacula");
146 init_msg(NULL, NULL);
147 working_directory = "/tmp";
148 args = get_pool_memory(PM_FNAME);
150 while ((ch = getopt(argc, argv, "bc:d:th?f:s:")) != -1) {
152 case 'c': /* configuration file */
153 if (configfile != NULL) {
156 configfile = bstrdup(optarg);
160 debug_level = atoi(optarg);
161 if (debug_level <= 0) {
185 if (configfile == NULL) {
186 configfile = bstrdup(CONFIG_FILE);
189 parse_config(configfile);
193 foreach_res(filed, R_CLIENT) {
194 items[nitems].type = R_CLIENT;
195 items[nitems].resource = filed;
196 items[nitems].D_sock = NULL;
197 items[nitems].state = warn;
200 foreach_res(stored, R_STORAGE) {
201 items[nitems].type = R_STORAGE;
202 items[nitems].resource = stored;
203 items[nitems].D_sock = NULL;
204 items[nitems].state = warn;
210 Emsg1(M_ERROR_TERM, 0, _("No Client nor Storage resource defined in %s\n\
211 Without that I don't how to get status from the File or Storage Daemon :-(\n"), configfile);
218 (void)WSA_Init(); /* Initialize Windows sockets */
221 monitor = (MONITOR*)GetNextRes(R_MONITOR, (RES *)NULL);
224 gtk_init (&argc, &argv);
226 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
227 // This should be ideally replaced by a completely libpr0n-based icon rendering.
228 mTrayIcon = egg_status_icon_new_from_pixbuf(pixbuf);
229 g_signal_connect(G_OBJECT(mTrayIcon), "activate", G_CALLBACK(TrayIconActivate), NULL);
230 g_signal_connect(G_OBJECT(mTrayIcon), "popup-menu", G_CALLBACK(TrayIconPopupMenu), NULL);
231 g_object_unref(G_OBJECT(pixbuf));
233 mTrayMenu = gtk_menu_new();
237 entry = gtk_menu_item_new_with_label("Open status window...");
238 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconActivate), NULL);
239 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
241 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
243 entry = gtk_menu_item_new_with_label("Exit");
244 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconExit), NULL);
245 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
247 gtk_widget_show_all(mTrayMenu);
249 timerTag = g_timeout_add( 5000/nitems, fd_read, NULL );
251 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
253 gtk_window_set_title(GTK_WINDOW(window), "Bacula tray monitor");
255 g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL);
256 //g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
258 gtk_container_set_border_width(GTK_CONTAINER(window), 10);
260 GtkWidget* vbox = gtk_vbox_new(FALSE, 10);
262 /*textview = gtk_text_view_new();
264 buffer = gtk_text_buffer_new(NULL);
266 gtk_text_buffer_set_text(buffer, "", -1);
268 PangoFontDescription *font_desc = pango_font_description_from_string ("Fixed 10");
269 gtk_widget_modify_font(textview, font_desc);
270 pango_font_description_free(font_desc);
272 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 20);
273 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 20);
275 gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
277 gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
279 gtk_box_pack_start(GTK_BOX(vbox), textview, TRUE, FALSE, 0);*/
281 GtkWidget* daemon_table = gtk_table_new((nitems*2)+1, 3, FALSE);
283 gtk_table_set_col_spacings(GTK_TABLE(daemon_table), 8);
285 GtkWidget* separator = gtk_hseparator_new();
286 gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, 0, 1);
289 for (int i = 0; i < nitems; i++) {
290 switch (items[i].type) {
292 str = g_string_new(((CLIENT*)(items[i].resource))->hdr.name);
293 g_string_append(str, _(" (FD)"));
296 str = g_string_new(((STORE*)(items[i].resource))->hdr.name);
297 g_string_append(str, _(" (SD)"));
303 GtkWidget* label = gtk_label_new(str->str);
304 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
305 items[i].image = gtk_image_new_from_pixbuf(pixbuf);
307 items[i].label = gtk_label_new(_("Unknown status."));
309 gtk_table_attach_defaults(GTK_TABLE(daemon_table), label, 0, 1, (i*2)+1, (i*2)+2);
310 gtk_table_attach_defaults(GTK_TABLE(daemon_table), items[i].image, 1, 2, (i*2)+1, (i*2)+2);
311 gtk_table_attach_defaults(GTK_TABLE(daemon_table), items[i].label, 2, 3, (i*2)+1, (i*2)+2);
313 GtkWidget* separator = gtk_hseparator_new();
314 gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, (i*2)+2, (i*2)+3);
317 gtk_box_pack_start(GTK_BOX(vbox), daemon_table, TRUE, FALSE, 0);
319 GtkWidget* hbox = gtk_hbox_new(FALSE, 10);
321 GtkWidget* button = new_image_button("gtk-help", "About");
322 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorAbout), NULL);
324 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
326 button = new_image_button("gtk-close", "Close");
327 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_hide), G_OBJECT(window));
329 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
331 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
333 gtk_container_add(GTK_CONTAINER (window), vbox);
335 gtk_widget_show_all(vbox);
339 for (i = 0; i < nitems; i++) {
340 if (items[i].D_sock) {
341 writecmd(&items[i], "quit");
342 bnet_sig(items[i].D_sock, BNET_TERMINATE); /* send EOF */
343 bnet_close(items[i].D_sock);
347 free_pool_memory(args);
348 (void)WSACleanup(); /* Cleanup Windows sockets */
352 static void MonitorAbout(GtkWidget *widget, gpointer data) {
354 GtkWidget* about = gtk_message_dialog_new_with_markup(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
355 "<span size='x-large' weight='bold'>Bacula Tray Monitor</span>\n\n"
356 "Copyright (C) 2004 Kern Sibbald and John Walker\n"
357 "Written by Nicolas Boichat\n"
358 "\n<small>Version: " VERSION " (" BDATE ") %s %s %s</small>"
359 ), HOST_OS, DISTNAME, DISTVER);
361 GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
362 "Bacula Tray Monitor\n\n"
363 "Copyright (C) 2004 Kern Sibbald and John Walker\n"
364 "Written by Nicolas Boichat\n"
365 "\nVersion: " VERSION " (" BDATE ") %s %s %s"
366 ), HOST_OS, DISTNAME, DISTVER);
368 gtk_dialog_run(GTK_DIALOG(about));
369 gtk_widget_destroy(about);
372 static gboolean delete_event( GtkWidget *widget,
375 gtk_widget_hide(window);
376 return TRUE; /* do not destroy the window */
379 static void TrayIconActivate(GtkWidget *widget, gpointer data) {
380 gtk_widget_show(window);
383 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) {
384 gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, 1, 0);
385 gtk_widget_show_all(mTrayMenu);
388 static void TrayIconExit(unsigned int activateTime, unsigned int button) {
392 static int authenticate_daemon(monitoritem* item, JCR *jcr) {
393 switch (item->type) {
395 return authenticate_file_daemon(jcr, monitor, (CLIENT*)item->resource);
398 return authenticate_storage_daemon(jcr, monitor, (STORE*)item->resource);
401 printf("Error, currentitem is not a Client or a Storage..\n");
407 static gboolean fd_read(gpointer data) {
408 GtkTextBuffer *newbuffer = gtk_text_buffer_new(NULL);
409 GtkTextIter start, stop, nstart, nstop;
412 stateenum stat = idle, nstat;
414 GString *strlast, *strcurrent;
417 if (lastupdated == nitems) {
421 nstat = getstatus(&items[lastupdated], 1, &strcurrent);
422 if (nstat > stat) stat = nstat;
423 nstat = getstatus(&items[lastupdated], 0, &strlast);
424 if (nstat > stat) stat = nstat;
426 changeStatusMessage(&items[lastupdated], "Current job: %s\nLast job: %s", strcurrent->str, strlast->str);
427 changeStatus(&items[lastupdated], stat);
429 g_string_free(strcurrent, TRUE);
430 g_string_free(strlast, TRUE);
432 if (lastupdated == fullitem) {
433 docmd(&items[lastupdated], "status", &list);
437 gtk_text_buffer_get_end_iter(newbuffer, &stop);
438 gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
439 if (it->data) g_string_free((GString*)it->data, TRUE);
440 } while ((it = it->next) != NULL);
442 /* Keep the selection if necessary */
443 if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
444 gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
445 gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop, gtk_text_iter_get_offset(&stop ));
448 gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
450 gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
451 gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
455 g_object_unref(buffer);
458 gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
464 stateenum getstatus(monitoritem* item, int current, GString** str) {
466 stateenum ret = error;
467 int jobid = 0, joberrors = 0;
468 char jobstatus = JS_ErrorTerminated;
471 *str = g_string_sized_new(128);
474 docmd(&items[lastupdated], ".status current", &list);
477 docmd(&items[lastupdated], ".status last", &list);
481 if ((it == NULL) || (strcmp(((GString*)it->data)->str, OKqstatus) != 0)) {
482 g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
485 else if ((it = it->next) == NULL) {
487 g_string_append(*str, _("No current job."));
490 g_string_append(*str, _("No last job."));
494 else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
497 ret = (joberrors > 0) ? warn : running;
498 g_string_append_printf(*str, _("Job status: Created (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
501 ret = (joberrors > 0) ? warn : running;
502 g_string_append_printf(*str, _("Job status: Running (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
505 ret = (joberrors > 0) ? warn : running;
506 g_string_append_printf(*str, _("Job status: Error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
509 g_string_append_printf(*str, _("Job status: Terminated (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
510 ret = (joberrors > 0) ? warn : idle;
512 case JS_ErrorTerminated:
513 g_string_append_printf(*str, _("Job status: Terminated in error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
517 g_string_append_printf(*str, _("Job status: Fatal error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
521 g_string_append_printf(*str, _("Job status: Blocked (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
525 g_string_append_printf(*str, _("Job status: Canceled (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
529 g_string_append_printf(*str, _("Job status: Unknown(%c) (%d error%s)"), jobstatus, joberrors, (joberrors > 1) ? "s" : "");
535 fprintf(stderr, "Bad scan : '%s' %d\n", (it == NULL) ? "" : ((GString*)it->data)->str, k);
541 if (it->data) g_string_free((GString*)it->data, TRUE);
542 } while ((it = it->next) != NULL);
549 int docmd(monitoritem* item, const char* command, GSList** list) {
553 *list = g_slist_alloc();
555 //str = g_string_sized_new(64);
558 memset(&jcr, 0, sizeof(jcr));
563 switch (item->type) {
565 filed = (CLIENT*)item->resource;
566 trayMessage("Connecting to Client %s:%d\n", filed->address, filed->FDport);
567 changeStatusMessage(item, "Connecting to Client %s:%d", filed->address, filed->FDport);
568 item->D_sock = bnet_connect(NULL, 0, 0, "File daemon", filed->address, NULL, filed->FDport, 0);
569 jcr.file_bsock = item->D_sock;
572 stored = (STORE*)item->resource;
573 trayMessage("Connecting to Storage %s:%d\n", stored->address, stored->SDport);
574 changeStatusMessage(item, "Connecting to Storage %s:%d", stored->address, stored->SDport);
575 item->D_sock = bnet_connect(NULL, 0, 0, "Storage daemon", stored->address, NULL, stored->SDport, 0);
576 jcr.store_bsock = item->D_sock;
579 printf("Error, currentitem is not a Client or a Storage..\n");
584 if (item->D_sock == NULL) {
585 g_slist_append(*list, g_string_new("Cannot connect to daemon.\n"));
586 changeStatusMessage(item, "Cannot connect to daemon.");
587 changeStatus(NULL, error);
591 if (!authenticate_daemon(item, &jcr)) {
592 str = g_string_sized_new(64);
593 g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
594 g_slist_append(*list, str);
596 changeStatus(NULL, error);
597 changeStatusMessage(item, "Authentication error : %s", item->D_sock->msg);
601 trayMessage("Opened connection with File daemon.\n");
602 changeStatusMessage(item, "Opened connection with File daemon.");
605 writecmd(item, command);
608 if ((stat = bnet_recv(item->D_sock)) >= 0) {
609 g_slist_append(*list, g_string_new(item->D_sock->msg));
611 else if (stat == BNET_SIGNAL) {
612 if (item->D_sock->msglen == BNET_EOD) {
615 else if (item->D_sock->msglen == BNET_HEARTBEAT) {
616 bnet_sig(item->D_sock, BNET_HB_RESPONSE);
617 g_slist_append(*list, g_string_new("<< Heartbeat signal received, answered. >>\n"));
620 str = g_string_sized_new(64);
621 g_string_printf(str, "<< Unexpected signal received : %s >>\n", bnet_sig_to_ascii(item->D_sock));
622 g_slist_append(*list, str);
625 else { /* BNET_HARDEOF || BNET_ERROR */
626 g_slist_append(*list, g_string_new("<ERROR>\n"));
628 changeStatus(NULL, error);
629 changeStatusMessage(item, "Error : BNET_HARDEOF or BNET_ERROR");
633 if (is_bnet_stop(item->D_sock)) {
634 g_string_append_printf(str, "<STOP>\n");
636 changeStatus(NULL, error);
637 changeStatusMessage(item, "Error : Connection closed.");
638 return 0; /* error or term */
643 void writecmd(monitoritem* item, const char* command) {
645 item->D_sock->msglen = strlen(command);
646 pm_strcpy(&item->D_sock->msg, command);
647 bnet_send(item->D_sock);
651 /* Note: Does not seem to work either on Gnome nor KDE... */
652 void trayMessage(const char *fmt,...) {
656 va_start(arg_ptr, fmt);
657 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
660 fprintf(stderr, buf);
662 egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
665 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
669 va_start(arg_ptr, fmt);
670 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
673 gtk_label_set_text(GTK_LABEL(item->label), buf);
676 void changeStatus(monitoritem* item, stateenum status) {
678 if (status == currentstatus)
682 if (status == item->state)
690 xpm = (const char**)&xpm_error;
693 xpm = (const char**)&xpm_idle;
696 xpm = (const char**)&xpm_running;
699 xpm = (const char**)&xpm_saving;
702 xpm = (const char**)&xpm_warn;
709 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
711 egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
712 gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
713 currentstatus = status;
716 gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
717 item->state = status;