]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/gnome2-console/console.c
This commit was manufactured by cvs2svn to create tag
[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 PangoFontDescription *font_desc;
52 PangoFontDescription *console_font_desc = NULL;
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  * Gtk2/pango have different font names. Gnome2 comes with "Monospace 10"
296  */
297
298    LockRes();
299    foreach_res(con_font, R_CONSOLE_FONT) {
300        if (!con_font->fontface) {
301           Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
302           continue;
303        }
304        Dmsg1(100, "Now loading: %s\n",con_font->fontface);
305         console_font_desc = pango_font_description_from_string(con_font->fontface);
306        if (console_font_desc == NULL) {
307            Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
308                   con_font->hdr.name, con_font->fontface);
309        } else {
310            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
311                   con_font->hdr.name, con_font->fontface);
312            break;
313        }
314    }
315    UnlockRes();
316
317    font_desc = pango_font_description_from_string("LucidaTypewriter 9");
318    gtk_widget_modify_font(console, font_desc);
319    gtk_widget_modify_font(entry1, font_desc);
320    gtk_widget_modify_font(status1, font_desc);
321    if (console_font_desc) {
322       gtk_widget_modify_font(text1, console_font_desc);
323       pango_font_description_free(console_font_desc);
324    } else {
325       gtk_widget_modify_font(text1, font_desc);
326    }
327    pango_font_description_free(font_desc);
328
329    if (test_config) {
330       terminate_console(0);
331       exit(0);
332    }
333
334    initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
335
336    gtk_main();
337    quit = true;
338    disconnect_from_director((gpointer)NULL);
339    return 0;
340 }
341
342 /*
343  * Every 5 seconds, ask the Director for our
344  *  messages.
345  */
346 static gint message_handler(gpointer data)
347 {
348    if (ready && UA_sock) {
349       bnet_fsend(UA_sock, ".messages");
350    }
351    return TRUE;
352 }
353
354 int disconnect_from_director(gpointer data)
355 {
356    if (!quit) {
357       set_status(_(" Not Connected"));
358    }
359    if (UA_sock) {
360       bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
361       bnet_close(UA_sock);
362       UA_sock = NULL;
363    }
364    return 1;
365 }
366
367 /*
368  * Called just after the main loop is started to allow
369  *  us to connect to the Director.
370  */
371 static int initial_connect_to_director(gpointer data)
372 {
373    gtk_timeout_remove(initial);
374    if (connect_to_director(data)) {
375       start_director_reader(data);
376    }
377    gtk_timeout_add(5000, message_handler, (gpointer)NULL);
378    return TRUE;
379 }
380
381 static GList *get_list(char *cmd)
382 {
383    GList *options;
384    char *msg;
385
386    options = NULL;
387    write_director(cmd);
388    while (bnet_recv(UA_sock) > 0) {
389       strip_trailing_junk(UA_sock->msg);
390       msg = (char *)malloc(strlen(UA_sock->msg) + 1);
391       strcpy(msg, UA_sock->msg);
392       options = g_list_append(options, msg);
393    }
394    return options;
395
396 }
397
398 static GList *get_and_fill_combo(GtkWidget *dialog, const char *combo_name, const char *dircmd)
399 {
400    GtkWidget *combo;
401    GList *options;
402
403    combo = lookup_widget(dialog, combo_name);
404    options = get_list((char *)dircmd);
405    if (combo && options) {
406       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
407    }
408    return options;
409 }
410
411 static void fill_combo(GtkWidget *dialog, const char *combo_name, GList *options)
412 {
413    GtkWidget *combo;
414
415    combo = lookup_widget(dialog, combo_name);
416    if (combo && options) {
417       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
418    }
419    return;
420 }
421
422
423 /*
424  * Connect to Director. If there are more than one, put up
425  * a modal dialog so that the user chooses one.
426  */
427 int connect_to_director(gpointer data)
428 {
429    GList *dirs = NULL;
430    GtkWidget *combo;
431    JCR jcr;
432
433
434    if (UA_sock) {
435       return 0;
436    }
437
438    if (ndir > 1) {
439       LockRes();
440       foreach_res(dir, R_DIRECTOR) {
441          dirs = g_list_append(dirs, dir->hdr.name);
442       }
443       UnlockRes();
444       dir_dialog = create_SelectDirectorDialog();
445       combo = lookup_widget(dir_dialog, "combo1");
446       dir_select = lookup_widget(dir_dialog, "dirselect");
447       if (dirs) {
448          gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);
449       }
450       gtk_widget_show(dir_dialog);
451       gtk_main();
452
453       if (reply == OK) {
454          gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
455          dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
456          if (ecmd) {
457             g_free(ecmd);             /* release director name string */
458          }
459       }
460       if (dirs) {
461          g_free(dirs);
462       }
463       gtk_widget_destroy(dir_dialog);
464       dir_dialog = NULL;
465    } else {
466       /* Just take the first Director */
467       LockRes();
468       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
469       UnlockRes();
470    }
471
472    if (!dir) {
473       return 0;
474    }
475
476    memset(&jcr, 0, sizeof(jcr));
477
478    set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
479    set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
480
481    while (gtk_events_pending()) {     /* fully paint screen */
482       gtk_main_iteration();
483    }
484
485    LockRes();
486    /* If cons==NULL, default console will be used */
487    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
488    UnlockRes();
489
490    char buf[1024];
491    /* Initialize Console TLS context */
492    if (cons && (cons->tls_enable || cons->tls_require)) {
493       /* Generate passphrase prompt */
494       bsnprintf(buf, sizeof(buf), _("Passphrase for Console \"%s\" TLS private key: "), cons->hdr.name);
495
496       /* Initialize TLS context:
497        * Args: CA certfile, CA certdir, Certfile, Keyfile,
498        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
499       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
500          cons->tls_ca_certdir, cons->tls_certfile,
501          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
502
503       if (!cons->tls_ctx) {
504          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Console \"%s\".\n"),
505             dir->hdr.name);
506          set_text(buf, strlen(buf));
507          terminate_console(0);
508          return 1;
509       }
510
511    }
512
513    /* Initialize Director TLS context */
514    if (dir->tls_enable || dir->tls_require) {
515       /* Generate passphrase prompt */
516       bsnprintf(buf, sizeof(buf), _("Passphrase for Director \"%s\" TLS private key: "), dir->hdr.name);
517
518       /* Initialize TLS context:
519        * Args: CA certfile, CA certdir, Certfile, Keyfile,
520        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
521       dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
522          dir->tls_ca_certdir, dir->tls_certfile,
523          dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
524
525       if (!dir->tls_ctx) {
526          bsnprintf(buf, sizeof(buf), _("Failed to initialize TLS context for Director \"%s\".\n"),
527             dir->hdr.name);
528          set_text(buf, strlen(buf));
529          terminate_console(0);
530          return 1;
531       }
532    }
533
534
535    UA_sock = bnet_connect(NULL, 5, 15, _("Director daemon"), dir->address,
536                           NULL, dir->DIRport, 0);
537    if (UA_sock == NULL) {
538       return 0;
539    }
540
541    jcr.dir_bsock = UA_sock;
542    if (!authenticate_director(&jcr, dir, cons)) {
543       set_text(UA_sock->msg, UA_sock->msglen);
544       return 0;
545    }
546
547    set_status(_(" Initializing ..."));
548
549    bnet_fsend(UA_sock, "autodisplay on");
550
551    /* Read and display all initial messages */
552    while (bnet_recv(UA_sock) > 0) {
553       set_text(UA_sock->msg, UA_sock->msglen);
554    }
555
556    /* Paint changes */
557    while (gtk_events_pending()) {
558       gtk_main_iteration();
559    }
560
561    /* Fill the run_dialog combo boxes */
562    job_list      = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
563    client_list   = get_and_fill_combo(run_dialog, "combo_client", ".clients");
564    fileset_list  = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
565    messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
566    pool_list     = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
567    storage_list  = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
568    type_list     = get_and_fill_combo(run_dialog, "combo_type", ".types");
569    level_list    = get_and_fill_combo(run_dialog, "combo_level", ".levels");
570
571    /* Fill the label dialog combo boxes */
572    fill_combo(label_dialog, "label_combo_storage", storage_list);
573    fill_combo(label_dialog, "label_combo_pool", pool_list);
574
575
576    /* Fill the restore_dialog combo boxes */
577    fill_combo(restore_dialog, "combo_restore_job", job_list);
578    fill_combo(restore_dialog, "combo_restore_client", client_list);
579    fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
580    fill_combo(restore_dialog, "combo_restore_pool", pool_list);
581    fill_combo(restore_dialog, "combo_restore_storage", storage_list);
582
583    set_status(_(" Connected"));
584    return 1;
585 }
586
587 void write_director(const gchar *msg)
588 {
589    if (UA_sock) {
590       at_prompt = false;
591       set_status(_(" Processing command ..."));
592       UA_sock->msglen = strlen(msg);
593       pm_strcpy(&UA_sock->msg, msg);
594       bnet_send(UA_sock);
595    }
596    if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
597       disconnect_from_director((gpointer)NULL);
598       gtk_main_quit();
599    }
600 }
601
602 extern "C"
603 void read_director(gpointer data, gint fd, GdkInputCondition condition)
604 {
605    int stat;
606
607    if (!UA_sock || UA_sock->fd != fd) {
608       return;
609    }
610    stat = bnet_recv(UA_sock);
611    if (stat >= 0) {
612       if (at_prompt) {
613          set_text("\n", 1);
614          at_prompt = false;
615       }
616       set_text(UA_sock->msg, UA_sock->msglen);
617       return;
618    }
619    if (is_bnet_stop(UA_sock)) {         /* error or term request */
620       gtk_main_quit();
621       return;
622    }
623    /* Must be a signal -- either do something or ignore it */
624    if (UA_sock->msglen == BNET_PROMPT) {
625       at_prompt = true;
626       set_status(_(" At prompt waiting for input ..."));
627    }
628    if (UA_sock->msglen == BNET_EOD) {
629       set_status_ready();
630    }
631    return;
632 }
633
634 static gint tag;
635
636 void start_director_reader(gpointer data)
637 {
638
639    if (director_reader_running || !UA_sock) {
640       return;
641    }
642    tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
643    director_reader_running = true;
644 }
645
646 void stop_director_reader(gpointer data)
647 {
648    if (!director_reader_running) {
649       return;
650    }
651    gdk_input_remove(tag);
652    gdk_input_remove(tag);
653    gdk_input_remove(tag);
654    gdk_input_remove(tag);
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    director_reader_running = false;
663 }
664
665
666
667 /* Cleanup and then exit */
668 void terminate_console(int sig)
669 {
670    static bool already_here = false;
671
672    if (already_here)                  /* avoid recursive temination problems */
673       exit(1);
674    already_here = true;
675    cleanup_tls();
676    disconnect_from_director((gpointer)NULL);
677    gtk_main_quit();
678    exit(0);
679 }
680
681
682 /* Buffer approx 2000 lines -- assume 60 chars/line */
683 #define MAX_TEXT_CHARS   (2000 * 60)
684 static int text_chars = 0;
685
686 static void truncate_text_chars()
687 {
688    GtkTextBuffer *textbuf;
689    GtkTextIter iter, iter2;
690    guint len;
691    int del_chars = MAX_TEXT_CHARS / 4;
692
693    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
694    len = gtk_text_buffer_get_char_count(textbuf);
695    gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
696    gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
697    gtk_text_buffer_delete (textbuf, &iter, &iter2);
698    text_chars -= del_chars;
699    len = gtk_text_buffer_get_char_count(textbuf);
700    gtk_text_iter_set_offset(&iter, len);
701 }
702
703 void set_textf(const char *fmt, ...)
704 {
705    va_list arg_ptr;
706    char buf[1000];
707    int len;
708    va_start(arg_ptr, fmt);
709    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
710    va_end(arg_ptr);
711    set_text(buf, len);
712 }
713
714 void set_text(const char *buf, int len)
715 {
716    GtkTextBuffer *textbuf;
717    GtkTextIter iter;
718    guint buf_len;
719
720    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
721    buf_len = gtk_text_buffer_get_char_count(textbuf);
722    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
723    gtk_text_buffer_insert(textbuf, &iter, buf, -1);
724    text_chars += len;
725    if (text_chars > MAX_TEXT_CHARS) {
726       truncate_text_chars();
727    }
728    set_scroll_bar_to_end();
729 }
730
731 void set_statusf(const char *fmt, ...)
732 {
733    va_list arg_ptr;
734    char buf[1000];
735    int len;
736    va_start(arg_ptr, fmt);
737    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
738    va_end(arg_ptr);
739    gtk_label_set_text(GTK_LABEL(status1), buf);
740 // set_scroll_bar_to_end();
741    ready = false;
742 }
743
744 void set_status_ready()
745 {
746    gtk_label_set_text(GTK_LABEL(status1), _(" Ready"));
747    ready = true;
748 // set_scroll_bar_to_end();
749 }
750
751 void set_status(const char *buf)
752 {
753    gtk_label_set_text(GTK_LABEL(status1), buf);
754 // set_scroll_bar_to_end();
755    ready = false;
756 }
757
758 static void set_scroll_bar_to_end(void)
759 {
760    GtkTextBuffer* textbuf = NULL;
761    GtkTextIter iter;
762    guint buf_len;
763
764    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
765    buf_len = gtk_text_buffer_get_char_count(textbuf);
766    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
767    gtk_text_iter_set_offset(&iter, buf_len);
768    gtk_text_buffer_place_cursor(textbuf, &iter);
769    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
770               gtk_text_buffer_get_mark(textbuf, "insert"),
771               0, TRUE, 0.0, 1.0);
772 }