]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/gnome2-console/console.c
Mark translatable strings in all source files.
[bacula/bacula] / bacula / src / gnome2-console / console.c
1 /*
2  *
3  *   Bacula GNOME Console interface to the Director
4  *
5  *     Kern Sibbald, March MMII
6  *
7  *     Version $Id$
8  */
9 /*
10    Copyright (C) 2002-2005 Kern Sibbald
11
12    This program is free software; you can redistribute it and/or
13    modify it under the terms of the GNU General Public License
14    version 2 as amended with additional clauses defined in the
15    file LICENSE in the main source directory.
16
17    This program is distributed in the hope that it will be useful,
18    but WITHOUT ANY WARRANTY; without even the implied warranty of
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
20    the file LICENSE for additional details.
21
22  */
23
24 #include "bacula.h"
25 #include "console.h"
26
27 #include "interface.h"
28 #include "support.h"
29
30 /* Imported functions */
31 int authenticate_director(JCR *jcr, DIRRES *director, CONRES *cons);
32 void select_restore_setup();
33
34 /* Dummy functions */
35 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
36
37 /* Exported variables */
38 GtkWidget *console;   /* application window */
39 GtkWidget *text1;            /* text window */
40 GtkWidget *entry1;           /* entry box */
41 GtkWidget *status1;          /* status bar */
42 GtkWidget *combo1;           /* director combo */
43 GtkWidget *scroll1;          /* main scroll bar */
44 GtkWidget *run_dialog;       /* run dialog */
45 GtkWidget *dir_dialog;       /* director selection dialog */
46 GtkWidget *restore_dialog;   /* restore dialog */
47 GtkWidget *restore_file_selection;
48 GtkWidget *dir_select;
49 GtkWidget *about1;           /* about box */
50 GtkWidget *label_dialog;
51 GdkFont   *text_font = NULL;
52 PangoFontDescription *font_desc;
53 pthread_mutex_t cmd_mutex = PTHREAD_MUTEX_INITIALIZER;
54 pthread_cond_t  cmd_wait;
55 char cmd[1000];
56 int reply;
57 BSOCK *UA_sock = NULL;
58 GList *job_list, *client_list, *fileset_list;
59 GList *messages_list, *pool_list, *storage_list;
60 GList *type_list, *level_list;
61
62 /* Forward referenced functions */
63 void terminate_console(int sig);
64
65 extern "C" {
66     static gint message_handler(gpointer data);
67     static int initial_connect_to_director(gpointer data);
68 }
69
70 static void set_scroll_bar_to_end(void);
71
72 /* Static variables */
73 static char *configfile = NULL;
74 static DIRRES *dir;
75 static int ndir;
76 static bool director_reader_running = false;
77 static bool at_prompt = false;
78 static bool ready = false;
79 static bool quit = false;
80 static guint initial;
81 static int numdir = 0;
82
83 #define CONFIG_FILE "./gnome-console.conf"   /* default configuration file */
84
85 static void usage()
86 {
87    fprintf(stderr, _(
88 "Copyright (C) 2002-2005 Kern Sibbald\n"
89 "\nVersion: %s (%s) %s %s %s\n\n"
90 "Usage: gnome-console [-s] [-c config_file] [-d debug_level] [config_file]\n"
91 "       -c <file>   set configuration file to file\n"
92 "       -dnn        set debug level to nn\n"
93 "       -s          no signals\n"
94 "       -t          test - read configuration and exit\n"
95 "       -?          print this message.\n"
96 "\n"), VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
97
98    exit(1);
99 }
100
101 /*
102  * Call-back for reading a passphrase for an encrypted PEM file
103  * This function uses getpass(), which uses a static buffer and is NOT thread-safe.
104  */
105 static int tls_pem_callback(char *buf, int size, const void *userdata)
106 {
107 #ifdef HAVE_TLS
108    const char *prompt = (const char *) userdata;
109    char *passwd;
110
111    passwd = getpass(prompt);
112    bstrncpy(buf, passwd, size);
113    return (strlen(buf));
114 #else
115    buf[0] = 0;
116    return 0;
117 #endif
118 }
119
120
121 /*
122  * Make a quick check to see that we have all the
123  * resources needed.
124  */
125 static int check_resources()
126 {
127    int xOK = true;
128    DIRRES *director;
129
130    LockRes();
131
132    numdir = 0;
133    foreach_res(director, R_DIRECTOR) {
134       numdir++;
135       /* tls_require implies tls_enable */
136       if (director->tls_require) {
137          if (have_tls) {
138             director->tls_enable = true;
139          } else {
140             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
141             xOK = false;
142             continue;
143          }
144       }
145
146       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && director->tls_enable) {
147          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
148                              " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
149                              " At least one CA certificate store is required.\n"),
150                              director->hdr.name, configfile);
151          xOK = false;
152       }
153    }
154    
155    if (numdir == 0) {
156       Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
157                           "Without that I don't how to speak to the Director :-(\n"), configfile);
158       xOK = false;
159    }
160
161    CONRES *cons;
162    /* Loop over Consoles */
163    foreach_res(cons, R_CONSOLE) {
164       /* tls_require implies tls_enable */
165       if (cons->tls_require) {
166          if (have_tls) {
167             cons->tls_enable = true;
168          } else {
169             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
170             xOK = false;
171             continue;
172          }
173       }
174
175       if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && cons->tls_enable) {
176          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
177                              " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
178                              cons->hdr.name, configfile);
179          xOK = false;
180       }
181    }
182
183    UnlockRes();
184
185    return xOK;
186 }
187
188
189 /*********************************************************************
190  *
191  *         Main Bacula GNOME Console -- User Interface Program
192  *
193  */
194 int main(int argc, char *argv[])
195 {
196    int ch, stat;
197    int no_signals = TRUE;
198    int test_config = FALSE;
199    int gargc = 1;
200    const char *gargv[2] = {"gnome-console", NULL};
201    CONFONTRES *con_font;
202
203    setlocale(LC_ALL, "");
204    bindtextdomain("bacula", LOCALEDIR);
205    textdomain("bacula");
206
207    init_stack_dump();
208    my_name_is(argc, argv, "gnome-console");
209    init_msg(NULL, NULL);
210    working_directory  = "/tmp";
211
212    struct sigaction sigignore;
213    sigignore.sa_flags = 0;
214    sigignore.sa_handler = SIG_IGN;
215    sigfillset(&sigignore.sa_mask);
216    sigaction(SIGPIPE, &sigignore, NULL);
217
218    if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
219       Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
220          strerror(stat));
221    }
222
223    gnome_init("bacula", VERSION, gargc, (char **)&gargv);
224
225    while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
226       switch (ch) {
227       case 'c':                    /* configuration file */
228          if (configfile != NULL)
229             free(configfile);
230          configfile = bstrdup(optarg);
231          break;
232
233       case 'd':
234          debug_level = atoi(optarg);
235          if (debug_level <= 0)
236             debug_level = 1;
237          break;
238
239       case 's':                    /* turn off signals */
240          no_signals = TRUE;
241          break;
242
243       case 't':
244          test_config = TRUE;
245          break;
246
247       case '?':
248       default:
249          usage();
250       }
251    }
252    argc -= optind;
253    argv += optind;
254
255
256    if (!no_signals) {
257       init_signals(terminate_console);
258    }
259
260    if (argc) {
261       usage();
262    }
263
264    if (configfile == NULL) {
265       configfile = bstrdup(CONFIG_FILE);
266    }
267
268    parse_config(configfile);
269
270    if (init_tls() != 0) {
271       Emsg0(M_ERROR_TERM, 0, _("TLS library initialization failed.\n"));
272    }
273
274    if (!check_resources()) {
275       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
276    }
277
278    console = create_console();
279    gtk_window_set_default_size(GTK_WINDOW(console), 800, 700);
280    run_dialog = create_RunDialog();
281    label_dialog = create_label_dialog();
282    restore_dialog = create_RestoreDialog();
283    about1 = create_about1();
284
285    text1 = lookup_widget(console, "text1");
286    entry1 = lookup_widget(console, "entry1");
287    status1 = lookup_widget(console, "status1");
288    scroll1 = lookup_widget(console, "scroll1");
289
290    select_restore_setup();
291
292    gtk_widget_show(console);
293
294 /*
295  * Thanks to Phil Stracchino for providing the font configuration code.
296  * original default:
297    text_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-koi8-r");
298  * this works for me:
299    text_font = gdk_font_load("-Bigelow & Holmes-lucida console-medium-r-semi condensed-*-12-0-100-100-m-0-iso8859-1");
300  * and, new automagic:font specification!
301  */
302
303    LockRes();
304    foreach_res(con_font, R_CONSOLE_FONT) {
305        if (!con_font->fontface) {
306           Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
307           continue;
308        }
309        text_font = gdk_font_load(con_font->fontface);
310        if (text_font == NULL) {
311            Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
312                   con_font->hdr.name, con_font->fontface);
313        } else {
314            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
315                   con_font->hdr.name, con_font->fontface);
316            break;
317        }
318    }
319    UnlockRes();
320
321    if (text_font == NULL) {
322        Dmsg1(400, "Attempting to load fallback font %s\n",
323               "-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-iso8859-1");
324        text_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-iso8859-1");
325    }
326    font_desc = pango_font_description_from_string("LucidaTypewriter 9");
327    gtk_widget_modify_font(console, font_desc);
328    gtk_widget_modify_font(text1, font_desc);
329    gtk_widget_modify_font(entry1, font_desc);
330    gtk_widget_modify_font(status1, font_desc);
331    pango_font_description_free(font_desc);
332
333    if (test_config) {
334       terminate_console(0);
335       exit(0);
336    }
337
338    initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
339
340    gtk_main();
341    quit = true;
342    disconnect_from_director((gpointer)NULL);
343    return 0;
344 }
345
346 /*
347  * Every 5 seconds, ask the Director for our
348  *  messages.
349  */
350 static gint message_handler(gpointer data)
351 {
352    if (ready && UA_sock) {
353       bnet_fsend(UA_sock, ".messages");
354    }
355    return TRUE;
356 }
357
358 int disconnect_from_director(gpointer data)
359 {
360    if (!quit) {
361       set_status(_(" Not Connected"));
362    }
363    if (UA_sock) {
364       bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
365       bnet_close(UA_sock);
366       UA_sock = NULL;
367    }
368    return 1;
369 }
370
371 /*
372  * Called just after the main loop is started to allow
373  *  us to connect to the Director.
374  */
375 static int initial_connect_to_director(gpointer data)
376 {
377    gtk_timeout_remove(initial);
378    if (connect_to_director(data)) {
379       start_director_reader(data);
380    }
381    gtk_timeout_add(5000, message_handler, (gpointer)NULL);
382    return TRUE;
383 }
384
385 static GList *get_list(char *cmd)
386 {
387    GList *options;
388    char *msg;
389
390    options = NULL;
391    write_director(cmd);
392    while (bnet_recv(UA_sock) > 0) {
393       strip_trailing_junk(UA_sock->msg);
394       msg = (char *)malloc(strlen(UA_sock->msg) + 1);
395       strcpy(msg, UA_sock->msg);
396       options = g_list_append(options, msg);
397    }
398    return options;
399
400 }
401
402 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *cm)
403 {
404    GtkWidget *combo;
405    GList *options;
406
407    combo = lookup_widget(dialog, combo_name);
408    options = get_list(cmd);
409    if (combo && options) {
410       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
411    }
412    return options;
413 }
414
415 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
416 {
417    GtkWidget *combo;
418
419    combo = lookup_widget(dialog, combo_name);
420    if (combo && options) {
421       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
422    }
423    return;
424 }
425
426
427 /*
428  * Connect to Director. If there are more than one, put up
429  * a modal dialog so that the user chooses one.
430  */
431 int connect_to_director(gpointer data)
432 {
433    GList *dirs = NULL;
434    GtkWidget *combo;
435    JCR jcr;
436
437
438    if (UA_sock) {
439       return 0;
440    }
441
442    if (ndir > 1) {
443       LockRes();
444       foreach_res(dir, R_DIRECTOR) {
445          dirs = g_list_append(dirs, dir->hdr.name);
446       }
447       UnlockRes();
448       dir_dialog = create_SelectDirectorDialog();
449       combo = lookup_widget(dir_dialog, "combo1");
450       dir_select = lookup_widget(dir_dialog, "dirselect");
451       if (dirs) {
452          gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
453       }
454       gtk_widget_show(dir_dialog);
455       gtk_main();
456
457       if (reply == OK) {
458          gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
459          dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
460          if (ecmd) {
461             g_free(ecmd);             /* release director name string */
462          }
463       }
464       if (dirs) {
465          g_free(dirs);
466       }
467       gtk_widget_destroy(dir_dialog);
468       dir_dialog = NULL;
469    } else {
470       /* Just take the first Director */
471       LockRes();
472       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
473       UnlockRes();
474    }
475
476    if (!dir) {
477       return 0;
478    }
479
480    memset(&jcr, 0, sizeof(jcr));
481
482    set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
483    set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
484
485    while (gtk_events_pending()) {     /* fully paint screen */
486       gtk_main_iteration();
487    }
488
489    LockRes();
490    /* If cons==NULL, default console will be used */
491    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
492    UnlockRes();
493
494    char buf[1024];
495    /* Initialize Console TLS context */
496    if (cons && (cons->tls_enable || cons->tls_require)) {
497       /* Generate passphrase prompt */
498       bsnprintf(buf, sizeof(buf), _("Passphrase for Console \"%s\" TLS private key: "), cons->hdr.name);
499
500       /* Initialize TLS context:
501        * Args: CA certfile, CA certdir, Certfile, Keyfile,
502        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
503       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
504          cons->tls_ca_certdir, cons->tls_certfile,
505          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
506
507       if (!cons->tls_ctx) {
508          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
509             dir->hdr.name);
510          set_text(buf, strlen(buf));
511          terminate_console(0);
512          return 1;
513       }
514
515    }
516
517    /* Initialize Director TLS context */
518    if (dir->tls_enable || dir->tls_require) {
519       /* Generate passphrase prompt */
520       bsnprintf(buf, sizeof(buf), _("Passphrase for Director \"%s\" TLS private key: "), dir->hdr.name);
521
522       /* Initialize TLS context:
523        * Args: CA certfile, CA certdir, Certfile, Keyfile,
524        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
525       dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
526          dir->tls_ca_certdir, dir->tls_certfile,
527          dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
528
529       if (!dir->tls_ctx) {
530          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
531             dir->hdr.name);
532          set_text(buf, strlen(buf));
533          terminate_console(0);
534          return 1;
535       }
536    }
537
538
539    UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
540                           NULL, dir->DIRport, 0);
541    if (UA_sock == NULL) {
542       return 0;
543    }
544
545    jcr.dir_bsock = UA_sock;
546    if (!authenticate_director(&jcr, dir, cons)) {
547       set_text(UA_sock->msg, UA_sock->msglen);
548       return 0;
549    }
550
551    set_status(_(" Initializing ..."));
552
553    bnet_fsend(UA_sock, "autodisplay on");
554
555    /* Read and display all initial messages */
556    while (bnet_recv(UA_sock) > 0) {
557       set_text(UA_sock->msg, UA_sock->msglen);
558    }
559
560    /* Paint changes */
561    while (gtk_events_pending()) {
562       gtk_main_iteration();
563    }
564
565    /* Fill the run_dialog combo boxes */
566    job_list      = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
567    client_list   = get_and_fill_combo(run_dialog, "combo_client", ".clients");
568    fileset_list  = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
569    messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
570    pool_list     = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
571    storage_list  = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
572    type_list     = get_and_fill_combo(run_dialog, "combo_type", ".types");
573    level_list    = get_and_fill_combo(run_dialog, "combo_level", ".levels");
574
575    /* Fill the label dialog combo boxes */
576    fill_combo(label_dialog, "label_combo_storage", storage_list);
577    fill_combo(label_dialog, "label_combo_pool", pool_list);
578
579
580    /* Fill the restore_dialog combo boxes */
581    fill_combo(restore_dialog, "combo_restore_job", job_list);
582    fill_combo(restore_dialog, "combo_restore_client", client_list);
583    fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
584    fill_combo(restore_dialog, "combo_restore_pool", pool_list);
585    fill_combo(restore_dialog, "combo_restore_storage", storage_list);
586
587    set_status(_(" Connected"));
588    return 1;
589 }
590
591 void write_director(const gchar *msg)
592 {
593    if (UA_sock) {
594       at_prompt = false;
595       set_status(_(" Processing command ..."));
596       UA_sock->msglen = strlen(msg);
597       pm_strcpy(&UA_sock->msg, msg);
598       bnet_send(UA_sock);
599    }
600    if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
601       disconnect_from_director((gpointer)NULL);
602       gtk_main_quit();
603    }
604 }
605
606 extern "C"
607 void read_director(gpointer data, gint fd, GdkInputCondition condition)
608 {
609    int stat;
610
611    if (!UA_sock || UA_sock->fd != fd) {
612       return;
613    }
614    stat = bnet_recv(UA_sock);
615    if (stat >= 0) {
616       if (at_prompt) {
617          set_text("\n", 1);
618          at_prompt = false;
619       }
620       set_text(UA_sock->msg, UA_sock->msglen);
621       return;
622    }
623    if (is_bnet_stop(UA_sock)) {         /* error or term request */
624       gtk_main_quit();
625       return;
626    }
627    /* Must be a signal -- either do something or ignore it */
628    if (UA_sock->msglen == BNET_PROMPT) {
629       at_prompt = true;
630       set_status(_(" At prompt waiting for input ..."));
631    }
632    if (UA_sock->msglen == BNET_EOD) {
633       set_status_ready();
634    }
635    return;
636 }
637
638 static gint tag;
639
640 void start_director_reader(gpointer data)
641 {
642
643    if (director_reader_running || !UA_sock) {
644       return;
645    }
646    tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
647    director_reader_running = true;
648 }
649
650 void stop_director_reader(gpointer data)
651 {
652    if (!director_reader_running) {
653       return;
654    }
655    gdk_input_remove(tag);
656    gdk_input_remove(tag);
657    gdk_input_remove(tag);
658    gdk_input_remove(tag);
659    gdk_input_remove(tag);
660    gdk_input_remove(tag);
661    gdk_input_remove(tag);
662    gdk_input_remove(tag);
663    gdk_input_remove(tag);
664    gdk_input_remove(tag);
665    gdk_input_remove(tag);
666    director_reader_running = false;
667 }
668
669
670
671 /* Cleanup and then exit */
672 void terminate_console(int sig)
673 {
674    static bool already_here = false;
675
676    if (already_here)                  /* avoid recursive temination problems */
677       exit(1);
678    already_here = true;
679    cleanup_tls();
680    disconnect_from_director((gpointer)NULL);
681    gtk_main_quit();
682    exit(0);
683 }
684
685
686 /* Buffer approx 2000 lines -- assume 60 chars/line */
687 #define MAX_TEXT_CHARS   (2000 * 60)
688 static int text_chars = 0;
689
690 static void truncate_text_chars()
691 {
692    GtkTextBuffer *textbuf;
693    GtkTextIter iter, iter2;
694    guint len;
695    int del_chars = MAX_TEXT_CHARS / 4;
696
697    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
698    len = gtk_text_buffer_get_char_count(textbuf);
699    gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
700    gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
701    gtk_text_buffer_delete (textbuf, &iter, &iter2);
702    text_chars -= del_chars;
703    len = gtk_text_buffer_get_char_count(textbuf);
704    gtk_text_iter_set_offset(&iter, len);
705 }
706
707 void set_textf(const char *fmt, ...)
708 {
709    va_list arg_ptr;
710    char buf[1000];
711    int len;
712    va_start(arg_ptr, fmt);
713    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
714    va_end(arg_ptr);
715    set_text(buf, len);
716 }
717
718 void set_text(const char *buf, int len)
719 {
720    GtkTextBuffer *textbuf;
721    GtkTextIter iter;
722    guint buf_len;
723
724    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
725    buf_len = gtk_text_buffer_get_char_count(textbuf);
726    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
727    gtk_text_buffer_insert(textbuf, &iter, buf, -1);
728    text_chars += len;
729    if (text_chars > MAX_TEXT_CHARS) {
730       truncate_text_chars();
731    }
732    set_scroll_bar_to_end();
733 }
734
735 void set_statusf(const char *fmt, ...)
736 {
737    va_list arg_ptr;
738    char buf[1000];
739    int len;
740    va_start(arg_ptr, fmt);
741    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
742    va_end(arg_ptr);
743    gtk_label_set_text(GTK_LABEL(status1), buf);
744 // set_scroll_bar_to_end();
745    ready = false;
746 }
747
748 void set_status_ready()
749 {
750    gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
751    ready = true;
752 // set_scroll_bar_to_end();
753 }
754
755 void set_status(const char *buf)
756 {
757    gtk_label_set_text(GTK_LABEL(status1), buf);
758 // set_scroll_bar_to_end();
759    ready = false;
760 }
761
762 static void set_scroll_bar_to_end(void)
763 {
764    GtkTextBuffer* textbuf = NULL;
765    GtkTextIter iter;
766    guint buf_len;
767
768    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
769    buf_len = gtk_text_buffer_get_char_count(textbuf);
770    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
771    gtk_text_iter_set_offset(&iter, buf_len);
772    gtk_text_buffer_place_cursor(textbuf, &iter);
773    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
774               gtk_text_buffer_get_mark(textbuf, "insert"),
775               0, TRUE, 0.0, 1.0);
776 }