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