/*

  The Xlogmaster
  
  a GTK+ based program to keep track of logfiles & devices
  Copyright (C) 1998 Georg C. F. Greve
  This is a GNU program
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
  
  Contact: 
           mailto:xlogmaster-bugs@gnu.org
           http://www.gnu.org/software/xlogmaster/
  Secondary sources:
           http://porter.desy.de/~greve/xlogmaster/
           http://www.fusebox.hanse.de/xlogmaster/
	 
*/

/*{{{  Header  */

#include "../config.h"
#include "sysinc.H"
#include "logclass.H"
#include "xlogmaster.H"
#include "extern.H"

#include "../graphics/logo.xpm"
#include "../graphics/logo_small.xpm"

/*}}}*/

/*{{{  Main  */
int main (int argc, char *argv[]){
  /* Parse commandline & initialize toolkit */
  gtk_set_locale();
  gtk_init (&argc, &argv);

  /* Do my own initialization stunts */
  init( argc, argv );

  /* create main window */
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_usize( GTK_WIDGET (window), width, height);
  gtk_signal_connect (GTK_OBJECT (window), "destroy",
		      GTK_SIGNAL_FUNC (quit), NULL);
  gtk_signal_connect (GTK_OBJECT (window), "delete_event",
		      GTK_SIGNAL_FUNC (delete_event), NULL);
  gtk_widget_set_uposition( window, xpos, ypos );

  /* create boxes */
  box =  gtk_hbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER (window), box);
  buttonbox = gtk_vbox_new(FALSE, 0);
  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
				  GTK_POLICY_AUTOMATIC, 
				  GTK_POLICY_AUTOMATIC );
  gtk_widget_set_usize(scrolled_window, 170, 0);
 
  gtk_container_add (GTK_CONTAINER (scrolled_window), buttonbox);
  gtk_widget_show(scrolled_window);
  gtk_box_pack_end(GTK_BOX(box), scrolled_window, FALSE, FALSE, 5);
 
  /* create textwindow */
  tooltips = gtk_tooltips_new();
  create_textwidget();

  /* do color stunts */
  color_init();
  
  /* Now setup the data structures and create the buttons */
  setup_logs();
  create_buttons(); // ... as said

  /* PopUp Window */
  gtk_widget_show(buttonbox);
  gtk_widget_show(box);
  gtk_widget_show(window);

  /* Create dialog boxes for "about" and notice */
  create_about();
  create_notice_window();

  /* start displaying first Log */
  if ( request_active > syslogs ) active = syslogs-1;
  else active = request_active;
  start_watchdog();
  activate();

  /* logs can be displayed now */
  enable();

  /* and now hand over control to toolkit */
  gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
  gtk_main ();
  return 0;
}
/*}}}*/

/*{{{  GTK toolkit subroutines  */
gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data){
  // we will attach this to all windows so the window managers "delete" signal
  // won't kill our windows (that's what returning TRUE means)
  return (TRUE);
}
void quit (GtkWidget *widget, gpointer *data){
  if ( terse == TRUE ){
    really_quit_button_pressed();
    return;
  }

  GList* buttons = g_list_alloc();
  GList* functions = g_list_alloc();
  
  buttons = g_list_append(buttons,
			  (gpointer) "\t   YES   \t");
  functions = g_list_append(functions,
			    (gpointer) really_quit_button_pressed );
  buttons = g_list_append(buttons,
			  (gpointer) "\t   NO   \t");
  functions = g_list_append(functions, NULL );

  if ( configuration_changed == TRUE )
    popup_dialog
      ("Really Quit ?", 
       "\n\t     There are unsaved changes.     \t\n-\n\t     Do you really want to quit ?     \t\n", 
       buttons, functions);
  else
    popup_dialog
      ("Really Quit ?", 
       "\n\t     Do you really want to quit ?     \t\n", 
       buttons, functions);
}
void really_quit_button_pressed(){
  disable();
  stop_watchdog();
  free_memory();
  gtk_main_quit();
}
void color_init(){
  cmap = gdk_colormap_get_system();
  alert.pixel = 0;
  base.pixel = prelight.pixel = 0;
  stdstyle = gtk_rc_get_style(window);
  if ( ! stdstyle ) stdstyle = gtk_widget_get_style(window);
  base.red = stdstyle->bg[GTK_STATE_NORMAL].red;
  base.green = stdstyle->bg[GTK_STATE_NORMAL].green;
  base.blue = stdstyle->bg[GTK_STATE_NORMAL].blue;
  prelight.red = stdstyle->bg[GTK_STATE_PRELIGHT].red;
  prelight.green = stdstyle->bg[GTK_STATE_PRELIGHT].green;
  prelight.blue = stdstyle->bg[GTK_STATE_PRELIGHT].blue;  
  alert.red = (guint)( 65535.0 * c_alert[0] );
  alert.green = (guint)( 65535.0 * c_alert[1] );
  alert.blue = (guint)( 65535.0 * c_alert[2] );
}
void create_textwidget(){
  /*

    Currently the GtkText Widget does not support the horizontal scrollbar, so
    this'll have to wait until it does...

    textwindow = gtk_text_new(NULL,NULL);
    gtk_text_set_editable(GTK_TEXT(textwindow), FALSE);
    gtk_text_set_word_wrap (GTK_TEXT(textwindow), wordwrap);
    textscrollbar = gtk_vscrollbar_new (GTK_TEXT(textwindow)->vadj);
    gtk_box_pack_end(GTK_BOX(box), textscrollbar, FALSE, FALSE, 0);
    
    
    GTK_TEXT(textwindow)->line_wrap = FALSE;
    GtkWidget* text_hscrollbar= gtk_hscrollbar_new (GTK_TEXT(textwindow)->hadj);
    GtkWidget* vbox = gtk_vbox_new(FALSE,0);
    gtk_adjustment_clamp_page(GTK_ADJUSTMENT(GTK_TEXT(textwindow)->hadj),
    0, 1000 );
    gtk_box_pack_start(GTK_BOX(vbox), textwindow, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(vbox), text_hscrollbar, FALSE, FALSE, 0);
    gtk_box_pack_end(GTK_BOX(box), vbox, TRUE, TRUE, 0);
    gtk_widget_show (text_hscrollbar);
    gtk_widget_show (vbox);
    

    gtk_widget_show (textscrollbar);
    gtk_widget_show (textwindow);
  
    return;
  */

  textwindow = gtk_text_new(NULL,NULL);
  gtk_text_set_editable(GTK_TEXT(textwindow), FALSE);
  gtk_text_set_word_wrap (GTK_TEXT(textwindow), wordwrap);
  textscrollbar = gtk_vscrollbar_new (GTK_TEXT(textwindow)->vadj);
  gtk_box_pack_end(GTK_BOX(box), textscrollbar, FALSE, FALSE, 0);
  gtk_box_pack_end(GTK_BOX(box), textwindow, TRUE, TRUE, 0);
  gtk_widget_show (textscrollbar);
  gtk_widget_show(textwindow);

  /* Fire up Menu */
  fire_up_menu(textwindow);
}
void create_buttons(){
  for ( int i = 0 ; i < syslogs ; i++ ){
    syslog[i]->button = gtk_button_new_with_label (syslog[i]->buttontext);
    gtk_signal_connect (GTK_OBJECT (syslog[i]->button), "clicked",
			GTK_SIGNAL_FUNC (button_pressed), (gpointer) i );
    gtk_box_pack_start(GTK_BOX(buttonbox), syslog[i]->button, FALSE, FALSE, 5);
    gtk_tooltips_set_tip(tooltips, syslog[i]->button, syslog[i]->help,syslog[i]->help);
    gtk_widget_show(syslog[i]->button);
    syslog[i]->style = gtk_style_copy(stdstyle);
    gtk_widget_set_style(syslog[i]->button, syslog[i]->style);
    gtk_style_unref(syslog[i]->style);
  }
  gtk_widget_grab_focus(syslog[0]->button);
}
void button_pressed(GtkWidget *widget, gpointer *data){
  if ( display_logs == DISABLED ) return;
  int pressed = (int) data;       // Which button has been pressed ?
  deactivate();                        // deactivate old "Log" object
  active = pressed;                 // set new one
  activate();                           // and activate it
}
void request_about(){
  gtk_window_position(GTK_WINDOW(about_dialog), GTK_WIN_POS_MOUSE);
  gtk_widget_show(about_dialog);
}
void about_ready(GtkWidget *widget, gpointer *pointer){
  gtk_widget_hide(about_dialog);
};
/*}}}*/

/*{{{  Data management  */
void setup_logs(){
  syslog = new Log*[fillups];

  for ( int i = 0 ; i < fillups ; i++ ){
    syslog[i] = new Log;
    syslog[i]->mode = mode[i];
    syslog[i]->filename = filename[i];
    if ( help[i] != NULL ) syslog[i]->help = help[i];
    else{
      syslog[i]->help = new char[strlen(syslog[i]->filename)];
      strcpy(syslog[i]->help, syslog[i]->filename);
    }
    syslog[i]->buttontext = buttontext[i];
    syslog[i]->interval = interval[i];
    syslog[i]->commandline = commandline[i];
    syslog[i]->tag = 0;
    syslog[i]->alert_tag = 0;
    syslog[i]->buffer = syslog[i]->rbuffer = NULL;

    // chunk gives the blocksize of the read for the watchdog
    // must be maxtext for anything else than TAIL, of course.
    syslog[i]->chunk = RDBLCK * syslog[i]->interval;
    if ( ( syslog[i]->mode != TAIL_FILE ) ||
	 ( syslog[i]->chunk > maxtext) ) syslog[i]->chunk = maxtext;

    /*
      Fillup the filter elements with some mind dizzying memory juggling
    */

    if ( filter[i] != NULL ){
      char **f = filter[i];
      char **fe = filter_exec[i];
      int *m = filter_mode[i];

      int x = 0;
      while ( f[x] != NULL ) x++;
      syslog[i]->filter = new Filter*[x+1];
      syslog[i]->filterclass = NO_FILTER;

      x = 0;
      while  ( f[x] != NULL ) {
	syslog[i]->filter[x] = new Filter();
	syslog[i]->filter[x]->init( f[x], m[x] );
	delete f[x];
	syslog[i]->filter[x]->execline = fe[x];
	gint fclass = determine_filter_class(syslog[i]->filter[x]->mode);
	syslog[i]->filterclass |= fclass;
	x++;
      }
      syslog[i]->filter[x] = NULL; // NULL Termination is crucial...
      sort_filters(syslog[i]->filter);
      delete f;
      delete fe;
      delete m;
    }
  }
  syslogs = fillups;

  /*
    We don't need the temporaries anymore
  */
  delete mode;
  delete filename;
  delete help;
  delete buttontext;
  delete interval;
  delete commandline;
  delete filter;
  delete filter_exec;
  delete filter_mode;
  fillups = 0;
}
void unsetup_logs(){
  for ( int i = 0 ; i < syslogs ; i++ ){
    remove_alert(i);                                              // clear alert routines & data
    gtk_widget_destroy(syslog[i]->button);
    delete syslog[i];
  }
  delete syslog;
  syslogs = 0;
  syslog = NULL;
}
void free_memory(){
  unsetup_logs();
  if ( load_path != NULL ) delete load_path;
  if ( save_path != NULL ) delete save_path;
  if ( log_path != NULL ) delete log_path;
  if ( alert_sound != NULL ) delete alert_sound;
  if ( notice_sound != NULL ) delete notice_sound;
  if ( uniconify_sound != NULL ) delete uniconify_sound;
  uniconify_sound = notice_sound = alert_sound = NULL;

#if HAVE_MLOCKALL
  if ( lockmem == TRUE ) munlockall();
#endif /* HAVE_MLOCKALL */

  delete read_buffer;
  read_buffer = NULL;
}
void disable(){
  display_logs = DISABLED;
}
void enable(){
  display_logs = ENABLED;
}
/* routine to determine all filter classes applicable */
gint determine_filter_class(gint mode){
  int i;
  int fclass = NO_FILTER;
  
  i = 0;
  while ( class1_filters[i] != -1 )
    if ( class1_filters[i++] & mode ) fclass |= CLASS1_FILTER;
  
  i = 0;
  while ( class0_filters[i] != -1 )
    if ( class0_filters[i++] & mode ) fclass |= CLASS0_FILTER;

  return fclass;
}
void sort_filters(Filter** filter){
  if ( filter == NULL ) return;
  int amount = 0;
  while ( filter[amount] != NULL ) amount++;
  if ( amount == 0 ) return;

  Filter** tmp = new Filter*[amount+1];
  int i, j, k;
  i = k = 0;
  gint current_mode = 0;
  while ( current_mode != -1 ){
    j = 0;
    while ( filter[j] != NULL ){
      if (( filter[j]->mode & CLASS0_MASK ) == current_mode )
	tmp[k++] = filter[j];
      j++;
    }
    current_mode = class0_filters[i++];
  }
  tmp[k] = NULL;

  k = 0;
  while ( tmp[k] != NULL ){
    filter[k] = tmp[k];
    k++;
  }
  filter[k] = NULL;

  delete tmp;
  return;
}
/*}}}*/

/*{{{  Initial preparations  */
/*{{{  Subroutines  */
void usage(){
  cout << endl << "The Xlogmaster - version " << VERSION << ", usage: " << endl << endl;
  cout << "xlogmaster [-activate <n>] [-alertcolor <r,g,b>] ";
  cout << "[-audio <on/off>] [-audio-alert <filename>/<off>] ";
  cout << "[-audio-notice <filename>/<off>] [-audio-uniconify <filename>/<off>] ";
  cout << "[-buffer <n>[bkm]] [-config <file>] [-fadeseconds <n>] [-fadesteps <n>] ";
  cout << "[-geometry widthxheight+x+y] [-gtkrc <file>] ";
  cout << "[-help ] ";
#if HAVE_MLOCKALL
  cout << "[-mlockall ] ";
#endif /* HAVE_MLOCKALL */
  cout << "[-notice-follows-mouse [YES/no]] ";
  cout << "[-silent] [-smallicon [YES/no]] [-terse] ";
  cout << "[-version ] [-wordwrap [YES/no]] ";
  cout << endl << endl;
  _exit(0);
}
void help_message(){
  cout << endl << "The Xlogmaster " << VERSION << endl << endl;
  cout << "The following options are known to the Xlogmaster:" << endl << endl;
  cout << "-help\t\t\tPrint this message and exit" << endl;
  cout << "-version\t\tPrint version info and exit" << endl << endl;
  cout << "-audio <on/off> \tTurns audio support on/off" << endl;
  cout << "-audio-alert <filename>/<off> Sets file for alert to filename or off" << endl;
  cout << "-audio-notice <filename>/<off> Sets file for notice to filename or off" << endl;
  cout << "-audio-uniconify <filename>/<off> Sets file for uniconify to filename or off" << endl;
  cout << "-buffer <n>[bkm]\tSets buffer length to n" << endl;
  cout << "-fadeseconds <n>\tSets seconds to fade from alert" << endl;
  cout << "-fadesteps <n>\t\tSets steps to fade from alert" << endl;
  cout << "-geometry widthxheight+x+y Sets geometry to come up with" << endl;
  cout << "-gtkrc <file>\t\tUses file as the gtkrc" << endl;
#if HAVE_MLOCKALL
  cout << "-mlockall\t\tTurns memory locking on (see manpage before turning on !!) " << endl;
#endif /* HAVE_MLOCKALL */
  cout << "-notice-follows-mouse [YES/no] Sets notice box to follow mouse" << endl;
  cout << "-silent \t\tsame as '-audio off'" << endl;
  cout << "-smallicon [YES/no]\tUse small icon for iconify" << endl;
  cout << "-terse\t\t\tSuppress safety dialogs" << endl;
  cout << "-wordwrap [YES/no]\tSet wordwrap for textbuffer" << endl;
  
  cout << endl << "All arguments are evaluated in the given order";
  cout << endl << "[YES/no] means that YES is default if argument is left out" << endl << endl;
  
  cout << "Report bugs to xlogmaster-bugs@gnu.org" << endl;
  cout << "For more detailed information see manpage or tutorial." << endl << endl;

  _exit(0);
}
void version_message(){
  cout << endl << "The Xlogmaster " << VERSION << endl;
  cout << "Copyright (C) 1998 by Georg C. F. Greve" << endl;
  cout << "This is a GNU program" << endl;
  cout << "The Xlogmaster comes with NO WARRANTY," << endl;
  cout << "to the extent permitted by law." << endl;
  cout << "You may redistribute copies of the Xlogmaster" << endl;
  cout << "under the terms of the GNU General Public License." << endl;
  cout << "For more information about these matters," << endl;
  cout << "see the files named COPYING." << endl << endl;
  _exit(0);
}
void eval_colorstring(char* string){
  c_alert[0] = c_alert[1] = c_alert[2] = 0;
  int rgb = 0;
  int x = 0;
  double base = 1;
  while( string[x] != 0 && rgb <= 2 ){
    char a = string[x];
    if ( a == '.' ){
      base = 0.1;
    } else if ( a == ',' ){
      rgb++;
      base = 1;
    } else {
      a -= '0';
      if ( a >= 0 && a <= 9 ){
	c_alert[rgb] += base * (double) a;
	if ( base < 1 ) base /= 10;
	else base *= 10;
      }
    }
    x++;
  }
}
long get_value(char* string, int* index){
  long value = 0;
  int y = *index;
  char a;
  a = string[y] - '0';
  while ( ( a >= 0 && a <= 9 ) ){
    value *= 10;
    value += a;
    y++;
    a = string[y] - '0';
  }
  *index = y+1;
  if ( string[y] == 'k' ) value *= 1024;
  else if ( string[y] == 'm' ) value *= 1048576;
  else *index = y;
  return value;
}
/*}}}*/
void init(int argc,char* argv[]){
/*{{{  Set startup defaults  */
  syslogs = 0; // Nothing to display (yet)
  fillups = 0; // When we enter here there is nothing to be filled up by definition
  request_active = 0; // By default the first log is being displayed  
  maxtext = MAXTEXT; // Default from config.H
  read_buffer = NULL;
  wordwrap = WORDWRAP; // Default from config.H
  small_icon = ICON_SMALL;
  xpos = ypos = -1;
  width = 400;
  height = 250;
  load_path = save_path = log_path = NULL;
  lockmem = FALSE;
  notice_follows_mouse = FALSE; // no following around is default... most people will
  // find it too annoying.
  configuration_changed = FALSE; // no change of course...

  /* for execute.cc: */
  cleanup_tag = pid_reference = 0;
  pid_list = g_list_alloc();

  /* for audio.cc: */
  alert_sound = new char[ strlen( STYLEDIR ) + strlen("/sound/") + 20 ];
  notice_sound = new char[ strlen( STYLEDIR ) + strlen("/sound/") + 20 ];
  uniconify_sound = new char[ strlen( STYLEDIR ) + strlen("/sound/") + 20 ];
  strcpy(alert_sound, STYLEDIR);
  strcat(alert_sound, "/sound/");
  strcpy(notice_sound, alert_sound);
  strcpy(uniconify_sound, alert_sound);
  strcat(alert_sound, "alert");
  strcat(notice_sound, "notice");
  strcat(uniconify_sound, "uniconify");
  sound = AUDIO; // comes from autoconf
  
  /* alert color */
  char* cstring = new char[strlen(XLM_ALERT_COLOR)+2];
  strcpy(cstring, XLM_ALERT_COLOR);
  int k = 0;
  while ( cstring[k] != 0 ){
    if ( cstring[k] == '-' ) cstring[k] = ',';
    k++;
  }
  eval_colorstring(cstring);
  delete cstring;
/*}}}*/

/*{{{  set PATH variable  */
/*
  modify the PATH environment variable to contain the scripts
  directory - this way the scripts don't need path information for
  getting executed...
*/
 gchar* system_path = NULL;
 system_path = (gchar*) getenv("PATH");
 gchar* xlm_path;
 xlm_path = (gchar*) new char [ strlen( STYLEDIR ) + strlen( system_path ) + 20 ];
 strcpy(xlm_path, STYLEDIR);
 strcat(xlm_path, "/scripts:");
 strcat(xlm_path, system_path);
 setenv( "PATH", xlm_path, 1 );
 delete xlm_path;
/*}}}*/

/*{{{  my own commandline things  */
  gint  x;
  gint opt_index;
  int gtkrc = FALSE;
  terse = FALSE;
  
  x = getopt_long_only(argc, argv, "", options, &opt_index);
  while ( x != -1 && x != '?' ){
    const gchar* opt = options[opt_index].name;
    
    if ( ! strcmp( opt, "activate" ) )
      {
	if ( optarg == NULL ) usage();
	int index = 0;
	request_active = get_value( optarg, &index ) - 1 ;
      } 
    else if ( ! strcmp( opt, "alertcolor" ) )
      {
	if ( optarg == NULL ) usage();
	eval_colorstring(optarg);
      } 
    else if ( ! strcmp( opt, "audio" ))
      { 
	if ( optarg == NULL ) usage();
	if ( ! strcmp( optarg, "on" ) ) sound = TRUE;
	if ( ! strcmp( optarg, "ON" ) ) sound = TRUE;
	if ( ! strcmp( optarg, "off" ) ) sound = FALSE;
	if ( ! strcmp( optarg, "OFF" ) ) sound = FALSE;
	if ( sound == FALSE ){
	  if ( alert_sound != NULL ) delete alert_sound;
	  if ( notice_sound != NULL ) delete notice_sound;
	  if ( uniconify_sound != NULL ) delete uniconify_sound;
	  uniconify_sound = notice_sound = alert_sound = NULL;
	}
      }
    else if ( ! strcmp( opt, "audio-alert" ) )
      { 
	if ( optarg == NULL ) usage();
	if ( ! strcmp( optarg, "off" ) ) alert_sound = NULL;
	else if ( ! strcmp( optarg, "OFF" ) ) alert_sound = NULL;
	else {
	  alert_sound = new char[strlen(optarg)+1];
	  strcpy(alert_sound, optarg);
	  sound = TRUE;
	}
      }
    else if ( ! strcmp( opt, "audio-notice" ) )
      { 
	if ( optarg == NULL ) usage();
	if ( ! strcmp( optarg, "off" ) ) notice_sound = NULL;
	else if ( ! strcmp( optarg, "OFF" ) ) notice_sound = NULL;
	else {
	  notice_sound = new char[strlen(optarg)+1];
	  strcpy(notice_sound, optarg);
	  sound = TRUE;
	}
      } 
    else if ( ! strcmp( opt, "audio-uniconify" ) )
      { 
	if ( optarg == NULL ) usage();
	if ( ! strcmp( optarg, "off" ) ) uniconify_sound = NULL;
	else if ( ! strcmp( optarg, "OFF" ) ) uniconify_sound = NULL;
	else {
	  uniconify_sound = new char[strlen(optarg)+1];
	  strcpy(uniconify_sound, optarg);
	  sound = TRUE;
	}
      } 
    else if ( ! strcmp( opt, "buffer" ) )
      {
	if ( optarg == NULL ) usage();
	int index = 0;
	maxtext = get_value( optarg, &index );
      }
    else if ( ! strcmp( opt, "config" ) )
      {
	if ( optarg == NULL ) usage();
	read_configuration(optarg);
      }
    else if ( ! strcmp( opt, "fadeseconds" ) )
      {
	if ( optarg == NULL ) usage();
	int index = 0;
	int seconds = 0;
	seconds = get_value( optarg, &index );
	if ( seconds < 1 ) seconds = 1;
	fade_step = (-1.0 * ( (gdouble) fade_base / ( 10 * seconds )));
      }
    else if ( ! strcmp( opt, "fadesteps" ) )
      {
	if ( optarg == NULL ) usage();
	int index = 0;
	steps = get_value( optarg, &index );
      }
    else if ( ! strcmp( opt, "geometry" ) )
      {
	if ( optarg == NULL ) usage();
	int index = 0;
	char* string = optarg;
	
	if ( string[index] != 'x' )
	  width = get_value( optarg, &index );
	if ( string[index] == 'x' ){
	  index++;
	  height = get_value( optarg, &index );
	}
	if ( string[index] == '+' ){
	  index++;
	  xpos = get_value( optarg, &index );
	}
	if ( string[index] == '+' ){
	  index++;
	  ypos = get_value( optarg, &index );
	}
      }
    else if ( ! strcmp( opt, "gtkrc" ) )
      {
	if ( optarg == NULL ) usage();
	gtkrc = TRUE;
	gtk_rc_parse(optarg);
      }
    else if  ( ! strcmp( opt, "help" ) )
      {
	help_message();
      }
#if HAVE_MLOCKALL
    else if ( ! strcmp( opt, "mlockall" ) )
      { 
	lockmem = TRUE;
      }
#endif /* HAVE_MLOCKALL */
    else if ( ! strcmp( opt, "notice-follows-mouse" ) )
      { 
	if ( optarg == NULL )
	  {
	    notice_follows_mouse = TRUE;
	  }
	else
	  {
	    if ( ! strcmp( optarg, "yes" ) ) notice_follows_mouse = TRUE;
	    if ( ! strcmp( optarg, "YES" ) ) notice_follows_mouse = TRUE;
	    if ( ! strcmp( optarg, "no" ) ) notice_follows_mouse = FALSE;
	    if ( ! strcmp( optarg, "NO" ) ) notice_follows_mouse = FALSE;
	  }
      }
    else if ( ! strcmp( opt, "silent" ) )
      { 
	sound = FALSE;
      }
    else if ( ! strcmp( opt, "smallicon" ) )
      { 
	if ( optarg == NULL )
	  {
	    small_icon = TRUE;
	  }
	else
	  { 
	    if ( ! strcmp( optarg, "yes" ) ) small_icon = TRUE;
	    if ( ! strcmp( optarg, "YES" ) ) small_icon = TRUE;
	    if ( ! strcmp( optarg, "no" ) ) small_icon = FALSE;
	    if ( ! strcmp( optarg, "NO" ) ) small_icon = FALSE;
	  }
      }
    else if ( ! strcmp( opt, "terse" ) )
      { 
	terse = TRUE;
      }
    else if ( ! strcmp( opt, "version" ) )
      {
	version_message();
      }
    else if ( ! strcmp( opt, "wordwrap" ) )
      { 
	if ( optarg == NULL )
	  {
	    small_icon = TRUE;
	  }
	else
	  { 
	    if ( ! strcmp( optarg, "yes" ) ) wordwrap = TRUE;
	    if ( ! strcmp( optarg, "YES" ) ) wordwrap = TRUE;
	    if ( ! strcmp( optarg, "no" ) ) wordwrap = FALSE;
	    if ( ! strcmp( optarg, "NO" ) ) wordwrap = FALSE;
	  }
      }
    
    x = getopt_long_only(argc, argv, "", options, &opt_index);
  };
  
  if ( optind < argc || x == '?' )
    usage();
   
/*}}}*/

/*{{{  System-wide GTKRC  */

 if ( gtkrc == FALSE ){
   char* gtkrc_fn;
   gtkrc_fn = new char[ strlen( STYLEDIR ) + strlen( GTKRC ) + 3 ];
   strcpy(gtkrc_fn, STYLEDIR);
   strcat(gtkrc_fn, "/");
   strcat(gtkrc_fn, GTKRC);
   gtk_rc_parse(gtkrc_fn);
   delete gtkrc_fn;
 }
/*}}}*/

/*{{{  User specific config file  */
  if (  fillups == 0 ){
    char* fname;
    char* hdir = NULL;
    hdir = getenv("HOME");
    if ( hdir == NULL ) hdir = getenv("USER");
    if ( hdir == NULL ) hdir = getenv("PWD");
    if ( hdir == NULL ) hdir = ".";
    fname = new char[ strlen(hdir) + strlen(CONFIGFILE) + 2 ];
    strcpy ( fname, hdir );
    strcat ( fname, "/" );
    strcat ( fname, CONFIGFILE );
    read_configuration(fname);
    delete fname;
  }
/*}}}*/

/*{{{  Some logs for new users  */
  if ( fillups == 0 ){
    /*
      Alright - since we don't want to pop up an empty window without
      any boxes we better find something to display... :-)

      Some standards should be /var/log/messages,
      /var/log/boot.msg, /var/adm/debug, so we'll give them for
      starters since the user didn't give us anything else.

      For demonstration of the "cat" mode we also have a continuous
      display of /proc/meminfo as a default
      
     */

    fillups = 4;
    mode = new int[fillups];
    filename = new char*[fillups];
    help = new char*[fillups];
    buttontext = new char*[fillups];
    interval = new int[fillups];
    commandline = new char**[fillups];
    filter = new char**[fillups];
    filter_exec = new char**[fillups];
    filter_mode = new int*[fillups];
    for ( int i = 0 ; i < fillups ; i++ ){
      filename[i] = new char[50];
      help[i] = new char[50];  
      buttontext[i] = new char[50];
    }

    /*
      /var/log/messages
    */
    mode[0] = TAIL_FILE;
    strcpy ( filename[0] , "/var/log/messages" );
    strcpy ( help[0] , "Display /var/log/messages file..." );
    strcpy ( buttontext[0] , "System Messages" );
    interval[0] = INTERVAL;
    commandline[0] = NULL;
    filter[0] = NULL;
    filter_exec[0] = NULL;
    filter_mode[0] = NULL;

    /*
      /var/adm/debug
    */
    mode[1] = TAIL_FILE;
    strcpy ( filename[1] , "/var/adm/debug");
    strcpy ( help[1] , "Display /var/adm/debug file...");
    strcpy (buttontext[1] , "Debugging Messages");
    interval[1] = INTERVAL;
    commandline[1] = NULL;
    filter[1] = NULL;
    filter_exec[1] = NULL;
    filter_mode[1] = NULL;
    
    /*
      /var/log/boot.msg
    */
    mode[2] = TAIL_FILE;
    strcpy ( filename[2] , "/var/log/boot.msg");
    strcpy (help[2] , "Display /var/log/boot.msg file...");
    strcpy (buttontext[2] , "Bootup Messages");
    interval[2] = INTERVAL;
    commandline[2] = NULL;
    filter[2] = NULL;
    filter_exec[2] = NULL;
    filter_mode[2] = NULL;

    /*
      /proc/meminfo
    */
    mode[3] = CAT_FILE;
    strcpy ( filename[3] , "/proc/meminfo");
    strcpy (help[3] , "Display memory usage via /proc/meminfo");
    strcpy (buttontext[3] , "Memory Status");
    interval[3] = 10; // show this one once a second no matter of
    // config.H setting... 
    commandline[3] = NULL;
    filter[3] = NULL;
    filter_exec[3] = NULL;
    filter_mode[3] = NULL;
    
  }
/*}}}*/

#if HAVE_MLOCKALL
// if user requests so, try to lock the xlogmaster in memory...
 if ( lockmem == TRUE ){
   cout << endl << "Trying to lock the Xlogmaster in memory.... ";

   int result = mlockall(MCL_FUTURE);  

   if ( result == -1 ){
     cout << "failed. Insufficient permission ?" << endl;
     cout << "see the manpage for more information !" << endl;
   } else {
     cout << "success." << endl;
     cout << "Xlogmaster will not get swapped out." << endl;
   }
 }
#endif /* HAVE_MLOCKALL */
 read_buffer = new char[maxtext+2];
}
/*}}}*/

/*{{{  Text output subroutines  */
void output_text(GtkWidget* target, char* buffer, long size){
  /*
    If no filter has been set we can just output and bail out
   */
  if ( syslog[active]->filter == NULL ||
       syslog[active]->filterclass == NO_FILTER ){
    gtk_text_insert (GTK_TEXT(target),NULL,NULL,NULL,buffer,size);
    return;
  }
  
  /*
    alright - there is a filter, so we need to process the output first.
  */
  
  buffer[size] = 0;
  GdkColor *raise, *lower;
  GdkColor *raise_bg, *lower_bg;
  GtkStyle *style;
  style = gtk_widget_get_style( target );
  raise = &style->fg[GTK_STATE_PRELIGHT];
  raise_bg = &style->bg[GTK_STATE_PRELIGHT];
  lower = &style->fg[GTK_STATE_INSENSITIVE];
  lower_bg = &style->bg[GTK_STATE_INSENSITIVE];
  long start = 0;
  long stop = start;
  while ( stop < size ){
    int action = NO_CHANGE;
    int i = 0;
    while ( syslog[active]->filter[i] != NULL && action != HIDE ){
      if ( determine_filter_class(syslog[active]->filter[i]->mode) & CLASS0_FILTER )
	if ( does_match(buffer, syslog[active]->filter[i]->pattern,
			stop, size ) == TRUE ) action |= syslog[active]->filter[i]->mode;
      
      i++;
    }

    action &= CLASS0_MASK;

    if ( action != NO_CHANGE ){
      // first backtrace our steps until the beginning or the last CR / LF
      while ( stop > start && buffer[stop] != '\n' && buffer[stop] != '\r' ) stop--;
      // and now step over any CR/LF's there may be to avoid ugly blank
      // lines when hiding them...
      while ( stop < size && (  buffer[stop] == '\n' || buffer[stop] == '\r' ) ) stop++;
      if ( start < stop )
	gtk_text_insert (GTK_TEXT(target),NULL,NULL,NULL,
			 (char*) (buffer+start), (stop-start));
      start = stop;
      stop++;
      // now step forward until end of buffer or next CR / LF 
      while ( stop < size && buffer[stop] != '\n' && buffer[stop] != '\r' ) stop++;

      if ( ! ( action & HIDE ) ){
	// we need to search once more for a possible HIDE override
	for ( long n = start ; n < stop ; n++ ){
	  i = 0;
	  while ( syslog[active]->filter[i] != NULL && action != HIDE ){
	    if ( syslog[active]->filter[i]->mode & HIDE )
	      if ( does_match(buffer, syslog[active]->filter[i]->pattern,
			      n, size ) == TRUE )
		action |= syslog[active]->filter[i]->mode;
	    i++;
	  }
	}
      }

      action &= CLASS0_MASK;

      if ( action & HIDE ){
	// output nothing if we wanna hide - this is simple... 
	// skip that silly NL - we don't want it...
	while ( stop < size && (  buffer[stop] == '\n' || buffer[stop] == '\r' ) ) stop++;
      } else if ( action & RAISE ){
	gtk_text_insert (GTK_TEXT(target),NULL, raise, raise_bg,
			 (char*) (buffer+start), (stop-start));
      } else if ( action & LOWER ){
	gtk_text_insert (GTK_TEXT(target),NULL, lower, lower_bg,
			 (char*) (buffer+start), (stop-start));
      }

      start = stop;
    } else stop++;
  }
  if ( start != stop )
    gtk_text_insert (GTK_TEXT(target),NULL,NULL,NULL,
		     (char*) (buffer+start), (stop-start));
}
int does_match(char* buffer, char* regexp, long index, long size){
  // the regexp is already all in capitals... so we don't have to 
  // worry about capitalizing it...
  int i = 0;
  char*  new_regexp;
  while ( regexp[i] != 0 && (long) ( index + i ) < size ){
    switch ( regexp[i] ){
    case '*':

      new_regexp = (char*) (regexp+i+1);
      while ( (long) ( index + i ) < size &&
	      buffer[index + i] != 10 &&
	      buffer[index + i] != 13 ){
	if ( does_match(buffer, new_regexp , index + i, size) == TRUE )
	  return TRUE;
	i++;
      }
      return FALSE;
     
      break;
    case '?':

      i++;
      break;

    default:

      char a = buffer[index + i];
      if ( a >= 'a' && a <= 'z' ) a-= 'a' - 'A';            // Capitalize
      if ( a != regexp[i] ) return FALSE;
      if ( a == '\n' || a == '\r' ) return FALSE;
      i++;

      break;
    }
  }
  if ( regexp[i] == 0 ) return TRUE;
  return FALSE;
}
/*}}}*/

/*{{{  Error procedures  */
void file_error(gchar* file){
  gchar* opener = "\n\t     Error while trying to access file     \t\n\n\t      ";
  gchar* closer = "      \t\n\n\t   please check path and permissions.     \t\n";
  gchar* text = new gchar[strlen(file) + strlen(opener) + strlen(closer) + 10 ];
  strcpy ( text, opener );
  strcat ( text, file );
  strcat ( text, closer );

  GList* buttons = g_list_alloc();
  GList* functions = g_list_alloc();
  buttons = g_list_append(buttons,
			  (gpointer) "\t   DISMISS   \t");
  functions = g_list_append(functions, NULL );

  popup_dialog("File Access Error", text, buttons, functions);
  delete text;
}
void fork_error(){
  GList* buttons = g_list_alloc();
  GList* functions = g_list_alloc();
  buttons = g_list_append(buttons,
			  (gpointer) "\t   DISMISS   \t");
  functions = g_list_append(functions, NULL );
  
  popup_dialog("Fork Error", "\n\t    Error while trying to spawn a new process !!!     \t\n", buttons, functions);
}

/*}}}*/

/*{{{  About Xlogmaster  */
void 
create_about()
{
  GtkWidget* button;
  about_dialog = gtk_window_new( GTK_WINDOW_DIALOG);
  gtk_signal_connect (GTK_OBJECT (about_dialog), "destroy",
		      GTK_SIGNAL_FUNC (about_ready), NULL);
  gtk_signal_connect (GTK_OBJECT (about_dialog), "delete_event",
		      GTK_SIGNAL_FUNC (delete_event), NULL);
  gtk_window_set_title (GTK_WINDOW (about_dialog), "About the Xlogmaster" );
  GtkWidget* vbox = gtk_vbox_new(FALSE, 5);
  gtk_container_add(GTK_CONTAINER (about_dialog), vbox);
  gtk_widget_show(vbox);
  button = gtk_button_new_with_label ("Resume");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      GTK_SIGNAL_FUNC (about_ready), 0 );
  gtk_box_pack_end (GTK_BOX (vbox), button, TRUE, TRUE, 5);
  gtk_tooltips_set_tip(tooltips, button, "...back to watching all those cool logfiles... :-)" ,"...back to watching all those cool logfiles... :-)");
  gtk_widget_show (button);

  GtkWidget* label;

  GdkPixmap *pixmap;
  GdkBitmap *mask;
  GtkWidget *logo_xpm;
  GtkStyle *style;
  style = gtk_widget_get_style( window );
  pixmap = (GdkPixmap*)
    gdk_pixmap_create_from_xpm_d( window->window,
				  &mask,
				  &style->bg[GTK_STATE_NORMAL],
				  logo);

  logo_xpm = gtk_pixmap_new (pixmap, mask);
  gtk_box_pack_start (GTK_BOX (vbox), logo_xpm,TRUE, TRUE, 0);
  gtk_widget_show(logo_xpm);    

  /*
    Make the pixmap the default for Iconification... :-)
  */
  gtk_widget_realize (window);
  
  GdkPixmap *pixmap_small;
  GdkBitmap *mask_small;
  
  pixmap_small = (GdkPixmap*)
    gdk_pixmap_create_from_xpm_d( window->window,
				  &mask_small,
				  &style->bg[GTK_STATE_NORMAL],
				  logo_small);

  small_logo_xpm = gtk_pixmap_new (pixmap_small, mask_small);
  
  if ( small_icon == TRUE ){
    gdk_window_set_icon (window->window, NULL,
			 pixmap_small, mask_small);
  } else {
    gdk_window_set_icon (window->window, NULL,
			 pixmap, mask);
  }
  gdk_window_set_icon_name(window->window, "The Xlogmaster");
  
#define VERS_INTRO "Version "
  char* versionlabel = new char[ strlen(VERSION) + strlen(VERS_INTRO) + 2];
  strcpy(versionlabel, VERS_INTRO);
  strcat(versionlabel, VERSION);
  label = gtk_label_new ( versionlabel );
  delete versionlabel;
  gtk_box_pack_start (GTK_BOX (vbox), label
		      ,TRUE, TRUE, 0);
  gtk_widget_show (label);

  label = gtk_label_new ( " \n \t \t Copyright (C) 1998 Georg C. F. Greve \n \t \t This is a GNU program \n \t \n \t This program is distributed in the hope that it will be useful, \t \n but WITHOUT ANY WARRANTY; without even the implied warranty of \n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the \n GNU General Public License for more details. \n \t \t \n \n If you experience any problems, discover any bugs, \n think some new feature would be great or just want to say \n how much you like this program, write an email to: \n \n \t xlogmaster-bugs@gnu.org \t \n \n \n Thanks to the GTK+ crew for writing this great toolkit, I like \n it a lot - and to my girlfriend Julia for liking me even when \n I'm in a coding frenzy & for the great logo... ;-) \n");
  gtk_box_pack_start (GTK_BOX (vbox), label
		      ,TRUE, TRUE, 0);
  gtk_widget_show (label);

 
}
/*}}}*/
