]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tray-monitor/tray-monitor.c
kes Activate Close button on tray-monitor window. This fixes
[bacula/bacula] / bacula / src / tray-monitor / tray-monitor.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2004-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Gnome Tray Monitor
31  *
32  *     Nicolas Boichat, August MMIV
33  *
34  *     Version $Id$
35  */
36
37
38 #include "bacula.h"
39 #include "tray-monitor.h"
40
41 #include "generic.xpm"
42
43 #define TRAY_DEBUG_MEMORY 0
44
45 /* Imported functions */
46 int authenticate_director(JCR *jcr, MONITOR *monitor, DIRRES *director);
47 int authenticate_file_daemon(JCR *jcr, MONITOR *monitor, CLIENT* client);
48 int authenticate_storage_daemon(JCR *jcr, MONITOR *monitor, STORE* store);
49
50 /* Dummy functions */
51 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
52
53 /* Forward referenced functions */
54 void writecmd(monitoritem* item, const char* command);
55 int docmd(monitoritem* item, const char* command, GSList** list);
56 void getstatus(monitoritem* item, int current, GString** str);
57
58 /* Static variables */
59 static char *configfile = NULL;
60 static MONITOR *monitor;
61 static JCR jcr;
62 static int nitems = 0;
63 static int fullitem = 0; //Item to be display in detailled status window
64 static int lastupdated = -1; //Last item updated
65 static monitoritem items[32];
66
67 /* Data received from DIR/FD/SD */
68 static char OKqstatus[]   = "%c000 OK .status\n";
69 static char DotStatusJob[] = "JobId=%d JobStatus=%c JobErrors=%d\n";
70
71 /* UI variables and functions */
72
73 extern "C" {
74 static gboolean fd_read(gpointer data);
75 static gboolean blink(gpointer data);
76 }
77
78 void trayMessage(const char *fmt,...);
79 void updateStatusIcon(monitoritem* item);
80 void changeStatusMessage(monitoritem* item, const char *fmt,...);
81 static const char** generateXPM(stateenum newstate, stateenum oldstate);
82
83 /* Callbacks */
84 static void TrayIconActivate(GtkWidget *widget, gpointer data);
85 static void TrayIconExit(unsigned int activateTime, unsigned int button);
86 static void TrayIconPopupMenu(unsigned int button, unsigned int activateTime);
87 static void MonitorAbout(GtkWidget *widget, gpointer data);
88 static void MonitorRefresh(GtkWidget *widget, gpointer data);
89 static void IntervalChanged(GtkWidget *widget, gpointer data);
90 static void DaemonChanged(GtkWidget *widget, monitoritem* data);
91 static gboolean delete_event(GtkWidget *widget, GdkEvent  *event, gpointer   data);
92
93 static guint timerTag;
94 static GtkStatusIcon *mTrayIcon;
95 static GtkWidget *mTrayMenu;
96 static GtkWidget *window;
97 static GtkWidget *textview;
98 static GtkTextBuffer *buffer;
99 static GtkWidget *timeoutspinner;
100 char** xpm_generic_var;
101 static gboolean blinkstate = TRUE;
102
103 PangoFontDescription *font_desc = NULL;
104
105 #define CONFIG_FILE "./tray-monitor.conf"   /* default configuration file */
106
107 static void usage()
108 {
109    fprintf(stderr, _(
110 PROG_COPYRIGHT
111 "Written by Nicolas Boichat (2004)\n"
112 "\nVersion: %s (%s) %s %s %s\n\n"
113 "Usage: tray-monitor [-c config_file] [-d debug_level]\n"
114 "       -c <file>     set configuration file to file\n"
115 "       -dnn          set debug level to nn\n"
116 "       -t            test - read configuration and exit\n"
117 "       -?            print this message.\n"
118 "\n"), 2004, VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
119 }
120
121 static GtkWidget *new_image_button(const gchar *stock_id,
122                                    const gchar *label_text)
123 {
124     GtkWidget *button;
125     GtkWidget *box;
126     GtkWidget *label;
127     GtkWidget *image;
128
129     button = gtk_button_new();
130
131     box = gtk_hbox_new(FALSE, 0);
132     gtk_container_set_border_width(GTK_CONTAINER(box), 2);
133     image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
134     label = gtk_label_new(label_text);
135
136     gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 3);
137     gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 3);
138
139     gtk_widget_show(image);
140     gtk_widget_show(label);
141
142     gtk_widget_show(box);
143
144     gtk_container_add(GTK_CONTAINER(button), box);
145
146     return button;
147 }
148
149 int sm_line = 0;
150
151 #if TRAY_DEBUG_MEMORY
152 gpointer smt_malloc(gsize    n_bytes) {
153    return sm_malloc("GLib", sm_line, n_bytes);
154 }
155   
156 gpointer smt_realloc(gpointer mem, gsize    n_bytes) {
157    return sm_realloc("GLib", sm_line, mem, n_bytes);
158 }
159
160 gpointer smt_calloc(gsize    n_blocks,
161                     gsize    n_block_bytes) {
162    return sm_calloc("GLib", sm_line, n_blocks, n_block_bytes);
163 }
164
165 void     smt_free(gpointer mem) {
166    sm_free("Glib", sm_line, mem);
167 }
168 #endif
169
170 /*********************************************************************
171  *
172  *         Main Bacula Tray Monitor -- User Interface Program
173  *
174  */
175 int main(int argc, char *argv[])
176 {
177 #if TRAY_DEBUG_MEMORY
178    GMemVTable smvtable;
179    smvtable.malloc = &smt_malloc;
180    smvtable.realloc = &smt_realloc;
181    smvtable.free = &smt_free;
182    smvtable.calloc = &smt_calloc;
183    smvtable.try_malloc = NULL;
184    smvtable.try_realloc = NULL;
185    g_mem_set_vtable(&smvtable);
186 #endif
187    
188    int ch, i;
189    bool test_config = false;
190    DIRRES* dird;
191    CLIENT* filed;
192    STORE* stored;
193    CONFONTRES *con_font;
194
195    setlocale(LC_ALL, "");
196    bindtextdomain("bacula", LOCALEDIR);
197    textdomain("bacula");
198
199    init_stack_dump();
200    my_name_is(argc, argv, "tray-monitor");
201    init_msg(NULL, NULL);
202    working_directory = "/tmp";
203
204    struct sigaction sigignore;
205    sigignore.sa_flags = 0;
206    sigignore.sa_handler = SIG_IGN;
207    sigfillset(&sigignore.sa_mask);
208    sigaction(SIGPIPE, &sigignore, NULL);
209
210    gtk_init(&argc, &argv);
211
212    while ((ch = getopt(argc, argv, "bc:d:th?f:s:")) != -1) {
213       switch (ch) {
214       case 'c':                    /* configuration file */
215          if (configfile != NULL) {
216             free(configfile);
217          }
218          configfile = bstrdup(optarg);
219          break;
220
221       case 'd':
222          debug_level = atoi(optarg);
223          if (debug_level <= 0) {
224             debug_level = 1;
225          }
226          break;
227
228       case 't':
229          test_config = true;
230          break;
231
232       case 'h':
233       case '?':
234       default:
235          usage();
236          exit(1);
237       }
238    }
239    argc -= optind;
240    //argv += optind;
241
242    if (argc) {
243       usage();
244       exit(1);
245    }
246
247    if (configfile == NULL) {
248       configfile = bstrdup(CONFIG_FILE);
249    }
250
251    parse_config(configfile);
252
253    LockRes();
254    nitems = 0;
255    foreach_res(monitor, R_MONITOR) {
256       nitems++;
257    }
258
259    if (nitems != 1) {
260       Emsg2(M_ERROR_TERM, 0,
261          _("Error: %d Monitor resources defined in %s. You must define one and only one Monitor resource.\n"), nitems, configfile);
262    }
263
264    nitems = 0;
265    foreach_res(dird, R_DIRECTOR) {
266       items[nitems].type = R_DIRECTOR;
267       items[nitems].resource = dird;
268       items[nitems].D_sock = NULL;
269       items[nitems].state = warn;
270       items[nitems].oldstate = warn;
271       nitems++;
272    }
273    foreach_res(filed, R_CLIENT) {
274       items[nitems].type = R_CLIENT;
275       items[nitems].resource = filed;
276       items[nitems].D_sock = NULL;
277       items[nitems].state = warn;
278       items[nitems].oldstate = warn;
279       nitems++;
280    }
281    foreach_res(stored, R_STORAGE) {
282       items[nitems].type = R_STORAGE;
283       items[nitems].resource = stored;
284       items[nitems].D_sock = NULL;
285       items[nitems].state = warn;
286       items[nitems].oldstate = warn;
287       nitems++;
288    }
289    UnlockRes();
290
291    if (nitems == 0) {
292       Emsg1(M_ERROR_TERM, 0, _("No Client, Storage or Director resource defined in %s\n"
293 "Without that I don't how to get status from the File, Storage or Director Daemon :-(\n"), configfile);
294    }
295
296    if (test_config) {
297       exit(0);
298    }
299
300    //Copy the content of xpm_generic in xpm_generic_var to be able to modify it
301    g_assert((xpm_generic_var = (char**)g_malloc(sizeof(xpm_generic))));
302    for (i = 0; i < (int)(sizeof(xpm_generic)/sizeof(const char*)); i++) {
303       g_assert((xpm_generic_var[i] = (char*)g_malloc((strlen(xpm_generic[i])+1)*sizeof(char))));
304       strcpy(xpm_generic_var[i], xpm_generic[i]);
305    }
306
307    (void)WSA_Init();                /* Initialize Windows sockets */
308
309    LockRes();
310    monitor = (MONITOR*)GetNextRes(R_MONITOR, (RES *)NULL);
311    UnlockRes();
312
313    if ((monitor->RefreshInterval < 1) || (monitor->RefreshInterval > 600)) {
314       Emsg2(M_ERROR_TERM, 0, _("Invalid refresh interval defined in %s\n"
315 "This value must be greater or equal to 1 second and less or equal to 10 minutes (read value: %d).\n"), configfile, monitor->RefreshInterval);
316    }
317
318    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(generateXPM(warn, warn));
319
320    mTrayIcon = gtk_status_icon_new_from_pixbuf(pixbuf);
321    gtk_status_icon_set_tooltip(mTrayIcon, _("Bacula daemon status monitor"));
322    g_signal_connect(G_OBJECT(mTrayIcon), "activate", G_CALLBACK(TrayIconActivate), NULL);
323    g_signal_connect(G_OBJECT(mTrayIcon), "popup-menu", G_CALLBACK(TrayIconPopupMenu), NULL);
324    g_object_unref(G_OBJECT(pixbuf));
325
326    mTrayMenu = gtk_menu_new();
327
328    GtkWidget *entry;
329
330    entry = gtk_menu_item_new_with_label(_("Open status window..."));
331    g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconActivate), NULL);
332    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
333
334    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
335
336    entry = gtk_menu_item_new_with_label(_("Exit"));
337    g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconExit), NULL);
338    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
339
340    gtk_widget_show_all(mTrayMenu);
341
342    timerTag = g_timeout_add( 1000*monitor->RefreshInterval/nitems, fd_read, NULL );
343
344    g_timeout_add( 1000, blink, NULL);
345
346    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
347
348    gtk_window_set_title(GTK_WINDOW(window), _("Bacula tray monitor"));
349
350    g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL);
351    //g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
352
353    gtk_container_set_border_width(GTK_CONTAINER(window), 10);
354
355    GtkWidget* vbox = gtk_vbox_new(FALSE, 10);
356
357    GtkWidget* daemon_table = gtk_table_new((nitems*2)+2, 3, FALSE);
358
359    gtk_table_set_col_spacings(GTK_TABLE(daemon_table), 8);
360
361    GtkWidget* separator = gtk_hseparator_new();
362    gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, 0, 1);
363
364    GString *str;
365    GSList *group = NULL;
366    GtkWidget* radio;
367    GtkWidget* align;
368
369    for (int i = 0; i < nitems; i++) {
370       switch (items[i].type) {
371       case R_DIRECTOR:
372          str = g_string_new(((DIRRES*)(items[i].resource))->hdr.name);
373          g_string_append(str, _(" (DIR)"));
374          break;
375       case R_CLIENT:
376          str = g_string_new(((CLIENT*)(items[i].resource))->hdr.name);
377          g_string_append(str, _(" (FD)"));
378          break;
379       case R_STORAGE:
380          str = g_string_new(((STORE*)(items[i].resource))->hdr.name);
381          g_string_append(str, _(" (SD)"));
382          break;
383       default:
384          continue;
385       }
386
387       radio = gtk_radio_button_new_with_label(group, str->str);
388       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), i == 0);
389       g_signal_connect(G_OBJECT(radio), "toggled", G_CALLBACK(DaemonChanged), &(items[i]));
390
391       pixbuf = gdk_pixbuf_new_from_xpm_data(generateXPM(warn, warn));
392       items[i].image = gtk_image_new_from_pixbuf(pixbuf);
393
394       items[i].label =  gtk_label_new(_("Unknown status."));
395       align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
396       gtk_container_add(GTK_CONTAINER(align), items[i].label);
397
398       gtk_table_attach(GTK_TABLE(daemon_table), radio, 0, 1, (i*2)+1, (i*2)+2,
399          GTK_FILL, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
400       gtk_table_attach(GTK_TABLE(daemon_table), items[i].image, 1, 2, (i*2)+1, (i*2)+2,
401          GTK_FILL, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
402       gtk_table_attach(GTK_TABLE(daemon_table), align, 2, 3, (i*2)+1, (i*2)+2,
403          (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
404
405       separator = gtk_hseparator_new();
406       gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, (i*2)+2, (i*2)+3);
407
408       group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
409    }
410
411    gtk_box_pack_start(GTK_BOX(vbox), daemon_table, FALSE, FALSE, 0);
412
413    textview = gtk_text_view_new();
414
415    buffer = gtk_text_buffer_new(NULL);
416
417    gtk_text_buffer_set_text(buffer, "", -1);
418
419   /*
420    * Gtk2/pango have different font names. Gnome2 comes with "Monospace 10"
421    */
422
423    LockRes();
424    foreach_res(con_font, R_CONSOLE_FONT) {
425        if (!con_font->fontface) {
426           Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
427           continue;
428        }
429        Dmsg1(100, "Now loading: %s\n",con_font->fontface);
430        font_desc = pango_font_description_from_string(con_font->fontface);
431        if (font_desc == NULL) {
432            Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
433                   con_font->hdr.name, con_font->fontface);
434        } else {
435            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
436                   con_font->hdr.name, con_font->fontface);
437            break;
438        }
439    }
440    UnlockRes();
441
442    if (!font_desc) {
443       font_desc = pango_font_description_from_string("Monospace 10");
444    }
445    if (!font_desc) {
446       font_desc = pango_font_description_from_string("monospace");
447    }
448
449    gtk_widget_modify_font(textview, font_desc);
450    pango_font_description_free(font_desc);
451
452    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 20);
453    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 20);
454
455    gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
456
457    gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
458
459    gtk_box_pack_start(GTK_BOX(vbox), textview, TRUE, TRUE, 0);
460
461    GtkWidget* hbox = gtk_hbox_new(FALSE, 10);
462
463    GtkWidget* hbox2 = gtk_hbox_new(FALSE, 0);
464    GtkWidget* label = gtk_label_new(_("Refresh interval in seconds: "));
465    gtk_box_pack_start(GTK_BOX(hbox2), label, TRUE, FALSE, 0);
466    GtkAdjustment *spinner_adj = (GtkAdjustment *) gtk_adjustment_new (monitor->RefreshInterval, 1.0, 600.0, 1.0, 5.0, 5.0);
467    timeoutspinner = gtk_spin_button_new (spinner_adj, 1.0, 0);
468    g_signal_connect(G_OBJECT(timeoutspinner), "value-changed", G_CALLBACK(IntervalChanged), NULL);
469    gtk_box_pack_start(GTK_BOX(hbox2), timeoutspinner, TRUE, FALSE, 0);
470    gtk_box_pack_start(GTK_BOX(hbox), hbox2, TRUE, FALSE, 0);
471
472    GtkWidget* button = new_image_button("gtk-refresh", _("Refresh now"));
473    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorRefresh), NULL);
474    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
475
476    button = new_image_button("gtk-help", _("About"));
477    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorAbout), NULL);
478    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
479
480    button = new_image_button("gtk-close", _("Close"));
481    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_hide), G_OBJECT(window));
482    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
483
484    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
485
486    gtk_container_add(GTK_CONTAINER (window), vbox);
487
488    gtk_widget_show_all(vbox);
489
490    gtk_main();
491    
492    g_source_remove(timerTag);
493    
494    sm_line = 0;
495
496    for (i = 0; i < nitems; i++) {
497       if (items[i].D_sock) {
498          switch (items[i].type) {
499          case R_DIRECTOR:
500             trayMessage(_("Disconnecting from Director %s:%d\n"), ((DIRRES*)items[i].resource)->address, ((DIRRES*)items[i].resource)->DIRport);
501             break;
502          case R_CLIENT:
503             trayMessage(_("Disconnecting from Client %s:%d\n"), ((CLIENT*)items[i].resource)->address, ((CLIENT*)items[i].resource)->FDport);
504             break;
505          case R_STORAGE:
506             trayMessage(_("Disconnecting from Storage %s:%d\n"), ((STORE*)items[i].resource)->address, ((STORE*)items[i].resource)->SDport);
507             break;          
508          default:
509             break;
510          }
511          //writecmd(&items[i], "quit");
512          bnet_sig(items[i].D_sock, BNET_TERMINATE); /* send EOF */
513          bnet_close(items[i].D_sock);
514       }
515    }
516
517    (void)WSACleanup();               /* Cleanup Windows sockets */
518
519    //Free xpm_generic_var
520    for (i = 0; i < (int)(sizeof(xpm_generic)/sizeof(const char*)); i++) {
521       g_free(xpm_generic_var[i]);
522    }
523    g_free(xpm_generic_var);
524    
525    gtk_object_destroy(GTK_OBJECT(window));
526    gtk_object_destroy(GTK_OBJECT(mTrayMenu));
527    term_msg();
528
529 #if TRAY_DEBUG_MEMORY
530    sm_dump(false);
531 #endif
532    
533    return 0;
534 }
535
536 static void MonitorAbout(GtkWidget *widget, gpointer data)
537 {
538 #if HAVE_GTK_2_4
539    GtkWidget* about = gtk_message_dialog_new_with_markup(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
540       "<span size='x-large' weight='bold'>%s</span>\n\n"
541       PROG_COPYRIGHT
542       "%s"
543       "\n<small>%s: %s (%s) %s %s %s</small>",
544       _("Bacula Tray Monitor"),
545         2004,
546       _("Written by Nicolas Boichat\n"),
547       _("Version"),
548       VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
549 #else
550    GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,   
551       "%s\n\n"
552       PROG_COPYRIGHT
553       "%s"
554       "\n%s: %s (%s) %s %s %s",
555       _("Bacula Tray Monitor"),
556         2004,
557       _("Written by Nicolas Boichat\n"),
558       _("Version"),
559       VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
560 #endif
561    gtk_dialog_run(GTK_DIALOG(about));
562    gtk_widget_destroy(about);
563 }
564
565 static void MonitorRefresh(GtkWidget *widget, gpointer data)
566 {
567    for (int i = 0; i < nitems; i++) {
568       fd_read(NULL);
569    }
570 }
571
572 static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
573 {
574    gtk_widget_hide(window);
575    return TRUE; /* do not destroy the window */
576 }
577
578 /*
579  * Come here when the user right clicks on the icon.
580  *   We display the icon menu.
581  */
582 static void TrayIconActivate(GtkWidget *widget, gpointer data)
583 {
584    gtk_widget_show(window);
585 }
586
587 /*
588  * Come here when the user left clicks on the icon. 
589  *  We popup the status window.
590  */
591 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) 
592 {
593    gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, button,
594                   gtk_get_current_event_time());
595    gtk_widget_show(mTrayMenu);
596 }
597
598 static void TrayIconExit(unsigned int activateTime, unsigned int button)
599 {
600    gtk_main_quit();
601 }
602
603 static void IntervalChanged(GtkWidget *widget, gpointer data)
604 {
605    g_source_remove(timerTag);
606    timerTag = g_timeout_add(
607       (guint)(
608          gtk_spin_button_get_value(GTK_SPIN_BUTTON(timeoutspinner))*1000/nitems
609       ), fd_read, NULL );
610 }
611
612 static void DaemonChanged(GtkWidget *widget, monitoritem* data) 
613 {
614    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
615       fullitem = -1;
616       for (int i = 0; i < nitems; i++) {
617          if (data == &(items[i])) {
618             fullitem = i;
619             break;
620          }
621       }
622       g_return_if_fail(fullitem != -1);
623
624       int oldlastupdated = lastupdated;
625       lastupdated = fullitem-1;
626       fd_read(NULL);
627       lastupdated = oldlastupdated;
628    }
629 }
630
631 static int authenticate_daemon(monitoritem* item, JCR *jcr) {
632    switch (item->type) {
633    case R_DIRECTOR:
634       return authenticate_director(jcr, monitor, (DIRRES*)item->resource);
635    case R_CLIENT:
636       return authenticate_file_daemon(jcr, monitor, (CLIENT*)item->resource);
637    case R_STORAGE:
638       return authenticate_storage_daemon(jcr, monitor, (STORE*)item->resource);
639    default:
640       printf(_("Error, currentitem is not a Client or a Storage..\n"));
641       gtk_main_quit();
642       return FALSE;
643    }
644    return false;
645 }
646
647 static gboolean blink(gpointer data) {
648    blinkstate = !blinkstate;
649    for (int i = 0; i < nitems; i++) {
650       updateStatusIcon(&items[i]);
651    }
652    updateStatusIcon(NULL);
653    return true;
654 }
655
656 static gboolean fd_read(gpointer data)
657 {
658    sm_line++;
659 #if TRAY_DEBUG_MEMORY
660    printf("sm_line=%d\n", sm_line);
661 #endif
662    GtkTextBuffer *newbuffer;
663    GtkTextIter start, stop, nstart, nstop;
664
665    GSList *list, *it;
666
667    GString *strlast, *strcurrent;
668
669    lastupdated++;
670    if (lastupdated == nitems) {
671       lastupdated = 0;
672    }
673
674    if (lastupdated == fullitem) {
675       newbuffer = gtk_text_buffer_new(NULL);
676       
677       if (items[lastupdated].type == R_DIRECTOR)
678          docmd(&items[lastupdated], "status Director\n", &list);
679       else
680          docmd(&items[lastupdated], "status\n", &list);
681
682       it = list->next;
683       do {
684          gtk_text_buffer_get_end_iter(newbuffer, &stop);
685          gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
686          if (it->data) g_string_free((GString*)it->data, TRUE);
687       } while ((it = it->next) != NULL);
688
689       g_slist_free(list);
690             
691       /* Keep the selection if necessary */
692       if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
693          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
694          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop,  gtk_text_iter_get_offset(&stop ));
695
696    #if HAVE_GTK_2_4
697          gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
698    #else
699          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
700          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
701    #endif
702       }
703
704       g_object_unref(buffer);
705
706       buffer = newbuffer;
707       gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
708    }
709
710    getstatus(&items[lastupdated], 1, &strcurrent);
711    getstatus(&items[lastupdated], 0, &strlast);
712    updateStatusIcon(&items[lastupdated]);
713
714    changeStatusMessage(&items[lastupdated], _("Current job: %s\nLast job: %s"), strcurrent->str, strlast->str);
715
716    updateStatusIcon(NULL);
717
718    g_string_free(strcurrent, TRUE);
719    g_string_free(strlast, TRUE);
720
721    return 1;
722 }
723
724 void append_error_string(GString* str, int joberrors) {
725    if (joberrors > 1) {
726       g_string_append_printf(str, _(" (%d errors)"), joberrors);
727    }
728    else {
729       g_string_append_printf(str, _(" (%d error)"), joberrors);
730    }
731 }
732
733 void getstatus(monitoritem* item, int current, GString** str)
734 {
735    GSList *list, *it;
736    stateenum ret = error;
737    int jobid = 0, joberrors = 0;
738    char jobstatus = JS_ErrorTerminated;
739    char num;
740    int k;
741
742    *str = g_string_sized_new(128);
743
744    if (current) {
745       if (item->type == R_DIRECTOR)
746          docmd(&items[lastupdated], ".status dir current\n", &list);
747       else
748          docmd(&items[lastupdated], ".status current\n", &list);
749    }
750    else {
751       if (item->type == R_DIRECTOR)
752          docmd(&items[lastupdated], ".status dir last\n", &list);
753       else
754          docmd(&items[lastupdated], ".status last\n", &list);
755    }
756
757    it = list->next;
758    if ((it == NULL) || (sscanf(((GString*)it->data)->str, OKqstatus, &num) != 1)) {
759       g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
760       while (((*str)->str[(*str)->len-1] == '\n') || ((*str)->str[(*str)->len-1] == '\r')) {
761          g_string_set_size(*str, (*str)->len-1);
762       }
763       ret = error;
764    }
765    else if ((it = it->next) == NULL) {
766       if (current) {
767          g_string_append(*str, _("No current job."));
768       }
769       else {
770          g_string_append(*str, _("No last job."));
771       }
772       ret = idle;
773    }
774    else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
775       switch (jobstatus) {
776       case JS_Created:
777          ret = (joberrors > 0) ? warn : running;
778          g_string_append_printf(*str, _("Job status: Created"));
779          append_error_string(*str, joberrors);
780          break;
781       case JS_Running:
782          ret = (joberrors > 0) ? warn : running;
783          g_string_append_printf(*str, _("Job status: Running"));
784          append_error_string(*str, joberrors);
785          break;
786       case JS_Blocked:
787          g_string_append_printf(*str, _("Job status: Blocked"));
788          append_error_string(*str, joberrors);
789          ret = warn;
790          break;
791       case JS_Terminated:
792          g_string_append_printf(*str, _("Job status: Terminated"));
793          append_error_string(*str, joberrors);
794          ret = (joberrors > 0) ? warn : idle;
795          break;
796       case JS_ErrorTerminated:
797          g_string_append_printf(*str, _("Job status: Terminated in error"));
798          append_error_string(*str, joberrors);
799          ret = error;
800          break;
801       case JS_Error:
802          ret = (joberrors > 0) ? warn : running;
803          g_string_append_printf(*str, _("Job status: Error"));
804          append_error_string(*str, joberrors);
805          break;
806       case JS_FatalError:
807          g_string_append_printf(*str, _("Job status: Fatal error"));
808          append_error_string(*str, joberrors);
809          ret = error;
810          break;
811       case JS_Differences:
812          g_string_append_printf(*str, _("Job status: Verify differences"));
813          append_error_string(*str, joberrors);
814          ret = warn;
815          break;
816       case JS_Canceled:
817          g_string_append_printf(*str, _("Job status: Canceled"));
818          append_error_string(*str, joberrors);
819          ret = warn;
820          break;
821       case JS_WaitFD:
822          g_string_append_printf(*str, _("Job status: Waiting on File daemon"));
823          append_error_string(*str, joberrors);
824          ret = warn;
825          break;
826       case JS_WaitSD:
827          g_string_append_printf(*str, _("Job status: Waiting on the Storage daemon"));
828          append_error_string(*str, joberrors);
829          ret = warn;
830          break;
831       case JS_WaitMedia:
832          g_string_append_printf(*str, _("Job status: Waiting for new media"));
833          append_error_string(*str, joberrors);
834          ret = warn;
835          break;
836       case JS_WaitMount:
837          g_string_append_printf(*str, _("Job status: Waiting for Mount"));
838          append_error_string(*str, joberrors);
839          ret = warn;
840          break;
841       case JS_WaitStoreRes:
842          g_string_append_printf(*str, _("Job status: Waiting for storage resource"));
843          append_error_string(*str, joberrors);
844          ret = warn;
845          break;
846       case JS_WaitJobRes:
847          g_string_append_printf(*str, _("Job status: Waiting for job resource"));
848          append_error_string(*str, joberrors);
849          ret = warn;
850          break;
851       case JS_WaitClientRes:
852          g_string_append_printf(*str, _("Job status: Waiting for Client resource"));
853          append_error_string(*str, joberrors);
854          ret = warn;
855          break;
856       case JS_WaitMaxJobs:
857          g_string_append_printf(*str, _("Job status: Waiting for maximum jobs"));
858          append_error_string(*str, joberrors);
859          ret = warn;
860          break;
861       case JS_WaitStartTime:
862          g_string_append_printf(*str, _("Job status: Waiting for start time"));
863          append_error_string(*str, joberrors);
864          ret = warn;
865          break;
866       case JS_WaitPriority:
867          g_string_append_printf(*str, _("Job status: Waiting for higher priority jobs to finish"));
868          append_error_string(*str, joberrors);
869          ret = warn;
870          break;
871       default:
872          g_warning(_("Unknown job status %c."), jobstatus);
873          g_string_append_printf(*str, _("Job status: Unknown(%c)"), jobstatus);
874          append_error_string(*str, joberrors);
875          ret = warn;
876          break;
877       }
878    }
879    else {
880       fprintf(stderr, _("Bad scan : '%s' %d\n"), (it == NULL) ? "" : ((GString*)it->data)->str, k);
881       ret = error;
882    }
883
884    it = list;
885    do {
886       if (it->data) {
887          g_string_free((GString*)it->data, TRUE);
888       }
889    } while ((it = it->next) != NULL);
890
891    g_slist_free(list);
892
893    if (current) {
894       item->state = ret;
895    }
896    else {
897       item->oldstate = ret;
898    }
899 }
900
901 int docmd(monitoritem* item, const char* command, GSList** list) 
902 {
903    int stat;
904    GString* str = NULL;
905
906    *list = g_slist_alloc();
907
908    //str = g_string_sized_new(64);
909
910    if (!item->D_sock) {
911       memset(&jcr, 0, sizeof(jcr));
912
913       DIRRES* dird;
914       CLIENT* filed;
915       STORE* stored;
916
917       switch (item->type) {
918       case R_DIRECTOR:
919          dird = (DIRRES*)item->resource;
920          trayMessage(_("Connecting to Director %s:%d\n"), dird->address, dird->DIRport);
921          changeStatusMessage(item, _("Connecting to Director %s:%d"), dird->address, dird->DIRport);
922          item->D_sock = bnet_connect(NULL, 0, 0, 0, _("Director daemon"), dird->address, NULL, dird->DIRport, 0);
923          jcr.dir_bsock = item->D_sock;
924          break;
925       case R_CLIENT:
926          filed = (CLIENT*)item->resource;
927          trayMessage(_("Connecting to Client %s:%d\n"), filed->address, filed->FDport);
928          changeStatusMessage(item, _("Connecting to Client %s:%d"), filed->address, filed->FDport);
929          item->D_sock = bnet_connect(NULL, 0, 0, 0, _("File daemon"), filed->address, NULL, filed->FDport, 0);
930          jcr.file_bsock = item->D_sock;
931          break;
932       case R_STORAGE:
933          stored = (STORE*)item->resource;
934          trayMessage(_("Connecting to Storage %s:%d\n"), stored->address, stored->SDport);
935          changeStatusMessage(item, _("Connecting to Storage %s:%d"), stored->address, stored->SDport);
936          item->D_sock = bnet_connect(NULL, 0, 0, 0, _("Storage daemon"), stored->address, NULL, stored->SDport, 0);
937          jcr.store_bsock = item->D_sock;
938          break;
939       default:
940          printf(_("Error, currentitem is not a Client, a Storage or a Director..\n"));
941          gtk_main_quit();
942          return 0;
943       }
944
945       if (item->D_sock == NULL) {
946          *list = g_slist_append(*list, g_string_new(_("Cannot connect to daemon.\n")));
947          changeStatusMessage(item, _("Cannot connect to daemon."));
948          item->state = error;
949          item->oldstate = error;
950          return 0;
951       }
952
953       if (!authenticate_daemon(item, &jcr)) {
954          str = g_string_sized_new(64);
955          g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
956          *list = g_slist_append(*list, str);
957          item->state = error;
958          item->oldstate = error;
959          changeStatusMessage(item, _("Authentication error : %s"), item->D_sock->msg);
960          item->D_sock = NULL;
961          return 0;
962       }
963
964       switch (item->type) {
965       case R_DIRECTOR:
966          trayMessage(_("Opened connection with Director daemon.\n"));
967          changeStatusMessage(item, _("Opened connection with Director daemon."));
968          break;
969       case R_CLIENT:
970          trayMessage(_("Opened connection with File daemon.\n"));
971          changeStatusMessage(item, _("Opened connection with File daemon."));
972          break;
973       case R_STORAGE:
974          trayMessage(_("Opened connection with Storage daemon.\n"));
975          changeStatusMessage(item, _("Opened connection with Storage daemon."));
976          break;
977       default:
978          printf(_("Error, currentitem is not a Client, a Storage or a Director..\n"));
979          gtk_main_quit();
980          return 0;
981          break;
982       }
983
984       if (item->type == R_DIRECTOR) { /* Read connection messages... */
985          GSList *tlist, *it;
986          docmd(item, "", &tlist); /* Usually invalid, but no matter */
987          it = tlist;
988          do {
989             if (it->data) {
990                g_string_free((GString*)it->data, TRUE);
991             }
992          } while ((it = it->next) != NULL);
993
994          g_slist_free(tlist);
995       }
996    }
997
998    if (command[0] != 0)
999       writecmd(item, command);
1000
1001    while(1) {
1002       if ((stat = bnet_recv(item->D_sock)) >= 0) {
1003          *list = g_slist_append(*list, g_string_new(item->D_sock->msg));
1004       }
1005       else if (stat == BNET_SIGNAL) {
1006          if (item->D_sock->msglen == BNET_EOD) {
1007             //fprintf(stderr, "<< EOD >>\n");
1008             return 1;
1009          }
1010          else if (item->D_sock->msglen == BNET_PROMPT) {
1011             //fprintf(stderr, "<< PROMPT >>\n");
1012             *list = g_slist_append(*list, g_string_new(_("<< Error: BNET_PROMPT signal received. >>\n")));
1013             return 0;
1014          }
1015          else if (item->D_sock->msglen == BNET_HEARTBEAT) {
1016             bnet_sig(item->D_sock, BNET_HB_RESPONSE);
1017             *list = g_slist_append(*list, g_string_new(_("<< Heartbeat signal received, answered. >>\n")));
1018          }
1019          else {
1020             str = g_string_sized_new(64);
1021             g_string_printf(str, _("<< Unexpected signal received : %s >>\n"), bnet_sig_to_ascii(item->D_sock));
1022             *list = g_slist_append(*list, str);
1023          }
1024       }
1025       else { /* BNET_HARDEOF || BNET_ERROR */
1026          *list = g_slist_append(*list, g_string_new(_("<ERROR>\n")));
1027          item->D_sock = NULL;
1028          item->state = error;
1029          item->oldstate = error;
1030          changeStatusMessage(item, _("Error : BNET_HARDEOF or BNET_ERROR"));
1031          //fprintf(stderr, _("<< ERROR >>\n"));
1032          return 0;
1033       }
1034
1035       if (is_bnet_stop(item->D_sock)) {
1036          g_string_append_printf(str, _("<STOP>\n"));
1037          item->D_sock = NULL;
1038          item->state = error;
1039          item->oldstate = error;
1040          changeStatusMessage(item, _("Error : Connection closed."));
1041          //fprintf(stderr, "<< STOP >>\n");
1042          return 0;            /* error or term */
1043       }
1044    }
1045 }
1046
1047 void writecmd(monitoritem* item, const char* command) {
1048    if (item->D_sock) {
1049       item->D_sock->msglen = strlen(command);
1050       pm_strcpy(&item->D_sock->msg, command);
1051       bnet_send(item->D_sock);
1052    }
1053 }
1054
1055 /* Note: Does not seem to work either on Gnome nor KDE... */
1056 void trayMessage(const char *fmt,...)
1057 {
1058    char buf[512];
1059    va_list arg_ptr;
1060
1061    va_start(arg_ptr, fmt);
1062    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1063    va_end(arg_ptr);
1064
1065    fprintf(stderr, buf);
1066
1067 // gtk_tray_icon_send_message(gtk_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
1068 }
1069
1070 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
1071    char buf[512];
1072    va_list arg_ptr;
1073
1074    va_start(arg_ptr, fmt);
1075    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1076    va_end(arg_ptr);
1077
1078    gtk_label_set_text(GTK_LABEL(item->label), buf);
1079 }
1080
1081 void updateStatusIcon(monitoritem* item) {
1082    const char** xpm;
1083
1084    if (item == NULL) {
1085       /* For the current status, select the two worse for blinking,
1086          but never blink a D_Sock == NULL error with idle. */
1087       stateenum state1, state2, oldstate;
1088       gboolean onenull = FALSE;
1089       state1 = idle;
1090       state2 = idle;
1091       oldstate = idle;
1092       for (int i = 0; i < nitems; i++) {
1093          if (items[i].D_sock == NULL) onenull = TRUE;
1094          if (items[i].state >= state1) {
1095             state2 = state1;
1096             state1 = items[i].state;
1097          }
1098          else if (items[i].state > state2) {
1099             state2 = items[i].state;
1100          }
1101          if (items[i].oldstate > oldstate) oldstate = items[i].oldstate;
1102       }
1103
1104       if ((onenull == TRUE) && (state2 == idle)) {
1105          state2 = error;
1106       }
1107
1108       xpm = generateXPM(blinkstate ? state1 : state2, oldstate);
1109    }
1110    else {
1111       if ((blinkstate) && (item->D_sock != NULL)) {
1112          if (item->state > 1) { //Warning or error while running
1113             xpm = generateXPM(running, item->oldstate);
1114          }
1115          else {
1116             xpm = generateXPM(idle, item->oldstate);
1117          }
1118       }
1119       else {
1120          xpm = generateXPM(item->state, item->oldstate);
1121       }
1122    }
1123
1124    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
1125    if (item == NULL) {
1126       gtk_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
1127       gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
1128    }
1129    else {
1130       gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
1131    }
1132    g_object_unref(G_OBJECT(pixbuf));
1133 }
1134
1135 /* Note: result should not be stored, as it is a reference to xpm_generic_var */
1136 static const char** generateXPM(stateenum newstate, stateenum oldstate) 
1137 {
1138    char* address = &xpm_generic_var[xpm_generic_first_color][xpm_generic_column];
1139    switch (newstate) {
1140    case error:
1141       strcpy(address, "ff0000");
1142       break;
1143    case idle:
1144       strcpy(address, "ffffff");
1145       break;
1146    case running:
1147       strcpy(address, "00ff00");
1148       break;
1149    case warn:
1150       strcpy(address, "ffff00");
1151       break;
1152    }
1153
1154    address = &xpm_generic_var[xpm_generic_second_color][xpm_generic_column];
1155    switch (oldstate) {
1156    case error:
1157       strcpy(address, "ff0000");
1158       break;
1159    case idle:
1160       strcpy(address, "ffffff");
1161       break;
1162    case running:
1163       strcpy(address, "00ff00");
1164       break;
1165    case warn:
1166       strcpy(address, "ffff00");
1167       break;
1168    }
1169
1170    return (const char**)xpm_generic_var;
1171 }