/* gitps.c -- A process viewer/killer utility.  */

/* Copyright (C) 1993-1998 Free Software Foundation, Inc.

   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Tudor Hulubei and Andrei Pitis.  */
/* $Id: gitps.c,v 1.8 1998/03/13 16:04:00 tudor Exp $ */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#else /* !HAVE_STDLIB_H */
#include "ansi_stdlib.h"
#endif /* !HAVE_STDLIB_H */

#include <sys/types.h>

#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif

#include <ctype.h>
#include <limits.h>
#include "file.h"
#include <fcntl.h>
#include <signal.h>

/* SVR2/SVR3.  */
#if !(defined(SIGCHLD)) && defined(SIGCLD)
#define SIGCHLD	SIGCLD
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#include <errno.h>

/* Not all systems declare ERRNO in errno.h... and some systems #define it! */
#if !defined (errno)
extern int errno;
#endif /* !errno */

#include <assert.h>

#include "stdc.h"
#include "xstring.h"
#include "xmalloc.h"
#include "getopt.h"
#include "tty.h"
#include "window.h"
#include "configure.h"
#include "tilde.h"
#include "signals.h"
#include "misc.h"


#define MAX_KEYS        2048
#define MAX_LINE	2048
#define PS_FIELDS         12


static char *PSFields[PS_FIELDS] =
{
    "TitleForeground",
    "TitleBackground",
    "TitleBrightness",
    "HeaderForeground",
    "HeaderBackground",
    "HeaderBrightness",
    "ScreenForeground",
    "ScreenBackground",
    "ScreenBrightness",
    "StatusForeground",
    "StatusBackground",
    "StatusBrightness"
};

#ifdef HAVE_LINUX
static int PSColors[PS_FIELDS] =
{
    CYAN, BLUE, ON, CYAN, RED, ON, BLACK, CYAN, OFF, CYAN, BLUE, ON
};
#else   /* !HAVE_LINUX */
static int PSColors[PS_FIELDS] =
{
    BLACK, WHITE, OFF, WHITE, BLACK, ON, WHITE, BLACK, OFF, BLACK, WHITE, OFF
};
#endif  /* !HAVE_LINUX */

#define TitleForeground                 PSColors[0]
#define TitleBackground                 PSColors[1]
#define TitleBrightness                 PSColors[2]
#define HeaderForeground                PSColors[3]
#define HeaderBackground                PSColors[4]
#define HeaderBrightness                PSColors[5]
#define ScreenForeground                PSColors[6]
#define ScreenBackground                PSColors[7]
#define ScreenBrightness                PSColors[8]
#define StatusForeground                PSColors[9]
#define StatusBackground                PSColors[10]
#define StatusBrightness                PSColors[11]


#ifdef HAVE_LINUX
extern int LinuxConsole;
#endif /* HAVE_LINUX */

#ifdef HAVE_LINUX
int AnsiColors = ON;
#else   /* !HAVE_LINUX */
int AnsiColors = OFF;
#endif  /* !HAVE_LINUX */


char color_section[]  = "[GITPS-Color]";
char monochrome_section[] = "[GITPS-Monochrome]";
int  processes;
int  PID_index;

char *home;
char *program;
char *ps_cmd;
char *temporary_directory;
char header_text[MAX_LINE];
int UseLastScreenChar;
int StartupScrollStep;
char *stdout_log_name;
char *stderr_log_name;
char **ps_vect;
char *screen;
char *global_buf;
int first_on_screen, current_process, scroll_step;
window_t *title_window, *header_window, *processes_window, *status_window;
static char *title_text;
static char *help;
static char no_perm[] = "not owner !";
static char no_proc[] = "no such process ! (REFRESH recommended)";


typedef struct
{
    char signame[10];
    int  signal;
} xsignal_t;

int signal_type = 0;

static xsignal_t sigdesc[] =
{
    { "SIGTERM  ", SIGTERM   },
    { "SIGABRT  ", SIGABRT   },
    { "SIGALRM  ", SIGALRM   },

#ifdef SIGBUS
    { "SIGBUS   ", SIGBUS    },
#endif

#ifdef SIGCHLD
    { "SIGCHLD  ", SIGCHLD   },
#endif

#ifdef SIGCLD
    { "SIGCLD   ", SIGCLD    },
#endif

#ifdef SIGCONT
    { "SIGCONT  ", SIGCONT   },
#endif

#ifdef SIGEMT
    { "SIGEMT   ", SIGEMT    },
#endif

#ifdef SIGFPE
    { "SIGFPE   ", SIGFPE    },
#endif

    { "SIGHUP   ", SIGHUP    },

#ifdef SIGILL
    { "SIGILL   ", SIGILL    },
#endif

#ifdef SIGINFO
    { "SIGINFO  ", SIGINFO   },
#endif

    { "SIGINT   ", SIGINT    },

#ifdef SIGIO
    { "SIGIO    ", SIGIO     },
#endif

#ifdef SIGIOT
    { "SIGIOT   ", SIGIOT    },
#endif

    { "SIGKILL  ", SIGKILL   },
    { "SIGPIPE  ", SIGPIPE   },

#ifdef SIGPOLL
    { "SIGPOLL  ", SIGPOLL   },
#endif

#ifdef SIGPROF
    { "SIGPROF  ", SIGPROF   },
#endif

#ifdef SIGPWR
    { "SIGPWR   ", SIGPWR    },
#endif

    { "SIGQUIT  ", SIGQUIT   },
    { "SIGSEGV  ", SIGSEGV   },

#ifdef SIGSTOP
    { "SIGSTOP  ", SIGSTOP   },
#endif

#ifdef SIGSYS
    { "SIGSYS   ", SIGSYS    },
#endif

#ifdef SIGTRAP
    { "SIGTRAP  ", SIGTRAP   },
#endif

#ifdef SIGTSTP
    { "SIGTSTP  ", SIGTSTP   },
#endif

#ifdef SIGTTIN
    { "SIGTTIN  ", SIGTTIN   },
#endif

#ifdef SIGTTOU
    { "SIGTTOU  ", SIGTTOU   },
#endif

#ifdef SIGURG
    { "SIGURG   ", SIGURG    },
#endif

    { "SIGUSR1  ", SIGUSR1   },
    { "SIGUSR2  ", SIGUSR2   },

#ifdef SIGVTALRM
    { "SIGVTALRM", SIGVTALRM },
#endif

#ifdef SIGWINCH
    { "SIGWINCH ", SIGWINCH  },
#endif

#ifdef SIGXCPU
    { "SIGXCPU  ", SIGXCPU   },
#endif

#ifdef SIGXFSZ
    { "SIGXFSZ  ", SIGXFSZ   },
#endif
};


#define BUILTIN_OPERATIONS              26


#define BUILTIN_previous_line            0
#define BUILTIN_next_line                1
#define BUILTIN_scroll_down              2
#define BUILTIN_scroll_up                3
#define BUILTIN_beginning_of_list        4
#define BUILTIN_end_of_list              5
#define BUILTIN_next_signal              6
#define BUILTIN_SIGHUP                   7
#define BUILTIN_SIGINT                   8
#define BUILTIN_SIGQUIT                  9
#define BUILTIN_SIGILL                  10
#define BUILTIN_SIGFPE                  11
#define BUILTIN_SIGKILL                 12
#define BUILTIN_SIGUSR1                 13
#define BUILTIN_SIGSEGV                 14
#define BUILTIN_SIGUSR2                 15
#define BUILTIN_SIGPIPE                 16
#define BUILTIN_SIGALRM                 17
#define BUILTIN_SIGTERM                 18
#define BUILTIN_SIGCHLD                 19
#define BUILTIN_SIGCONT                 20
#define BUILTIN_kill_process            21
#define BUILTIN_refresh                 22
#define BUILTIN_exit                    23
#define BUILTIN_hard_refresh            24


#define MAX_BUILTIN_NAME                20

char built_in[BUILTIN_OPERATIONS][MAX_BUILTIN_NAME] =
{
    "previous-line",
    "next-line",
    "scroll-down",
    "scroll-up",
    "beginning-of-list",
    "end-of-list",
    "next-signal",
    "SIGHUP",
    "SIGINT",
    "SIGQUIT",
    "SIGILL",
    "SIGFPE",
    "SIGKILL",
    "SIGUSR1",
    "SIGSEGV",
    "SIGUSR2",
    "SIGPIPE",
    "SIGALRM",
    "SIGTERM",
    "SIGCHLD",
    "SIGCONT",
    "kill-process",
    "refresh",
    "exit",
    "hard-refresh",
};


void
remove_log()
{
    if (stdout_log_name)
	unlink(stdout_log_name);

    if (stderr_log_name)
	unlink(stderr_log_name);
}


void
set_title()
{
    memset(global_buf, ' ', tty_columns);
    memcpy(global_buf, title_text,
	   min(tty_columns, (int)strlen(title_text)));

    tty_colors(TitleBrightness, TitleForeground, TitleBackground);

    window_goto(title_window, 0, 0);
    window_puts(title_window, global_buf, tty_columns);
}


void
set_header()
{
    memset(global_buf, ' ', tty_columns);
    memcpy(global_buf, header_text,
	   min(tty_columns, (int)strlen(header_text)));

    tty_colors(HeaderBrightness, HeaderForeground, HeaderBackground);

    window_goto(header_window, 0, 0);
    window_puts(header_window, global_buf, tty_columns);
}


void
set_status(what)
   char *what;
{
    memset(global_buf, ' ', tty_columns);

    if (what)
	memcpy(global_buf, what, min(tty_columns, (int)strlen(what)));
    else
	memcpy(global_buf, help, min(tty_columns, (int)strlen(help)));

    tty_colors(StatusBrightness, StatusForeground, StatusBackground);

    window_goto(status_window, 0, 0);

    if (tty_columns < (int)((sizeof(sigdesc[0].signame) - 1) + 1))
	window_puts(status_window, global_buf, tty_columns);
    else
    {
	global_buf[tty_columns - 1 - (sizeof(sigdesc[0].signame)-1) - 1] = ' ';
	window_puts(status_window, global_buf,
		    tty_columns - (sizeof(sigdesc[0].signame) - 1) - 1);
    }
}


void
set_signal()
{
    int len = sizeof(sigdesc[0].signame) - 1;

    if (tty_columns > len)
    {
	tty_colors(StatusBrightness, WHITE, StatusBackground);
	window_goto(status_window, 0, tty_columns - len - 1);
	window_puts(status_window, sigdesc[signal_type].signame, len);

	if (UseLastScreenChar == ON)
	    window_putc(status_window, ' ');
    }

    window_goto(status_window, 0, tty_columns - 1);
}


void
free_ps_list()
{
    int i;

    for (i = 0; i < processes; i++)
    {
	xfree(ps_vect[i]);
	ps_vect[i] = NULL;
    }
}


char *
read_ps_line(ps_output, line)
    FILE *ps_output;
    char *line;
{
    int c;
    char *ok;
    size_t lastchar;

    ok = fgets(line, MAX_LINE - 1, ps_output);

    if (ok == NULL)
	return NULL;

    if (line[lastchar = strlen(line) - 1] == '\n')
	line[lastchar] = 0;
    else
	while ((c = fgetc(ps_output)) != '\n' && c != EOF)
	    ;

    return ok;
}


int
get_PID_index(ps_output)
    FILE *ps_output;
{
    int i;
    char *h = header_text;

    if (read_ps_line(ps_output, header_text) == NULL)
	return -1;

    if (strstr(header_text, "PID") == NULL)
	return -1;

    for (i = 0; ; i++)
    {
	while (isspace(*h))
	    h++;

	if (memcmp(h, "PID", 3) == 0)
	    return i;

	while (!isspace(*h))
	    h++;
    }
}


int
kill_process(process_index)
    int process_index;
{
    int i;
    char *p;
    char pidstr[128];

    assert(process_index < processes);
    p = ps_vect[process_index];
    assert(p);

    for (i = 0; i < PID_index; i++)
    {
	while (isspace(*p))
	    p++;

	if (memcmp(p, "PID", 3) == 0)
	    return i;

	while (!isspace(*p))
	    p++;
    }

    i = 0;

    while (isspace(*p))
	p++;

    while (!isspace(*p))
	pidstr[i++] = *p++;

    pidstr[i] = 0;

    return !kill(atoi(pidstr), sigdesc[signal_type].signal);
}


void
build_ps_list(ps_output)
    FILE *ps_output;
{
    int i;
    char line[MAX_LINE];

    for (i = 0; read_ps_line(ps_output, line); i++)
    {
	ps_vect = (char **)xrealloc(ps_vect, (i + 1) * sizeof(char *));
	ps_vect[i] = xstrdup(line);
    }

    processes = i;
}


void
update_process(process, update_color)
    int process, update_color;
{
    assert(process < processes);

    memset(global_buf, ' ', tty_columns);
    memcpy(global_buf, ps_vect[process],
	   min(tty_columns, (int)strlen(ps_vect[process]) - 1));

    if (update_color)
    {
	tty_brightness(ScreenBrightness);

	if (process == current_process)
	{
	    tty_foreground(ScreenBackground);
	    tty_background(ScreenForeground);
	}
	else
	{
	    tty_foreground(ScreenForeground);
	    tty_background(ScreenBackground);
	}
    }

    window_goto(processes_window, process - first_on_screen, 0);
    window_puts(processes_window, global_buf, tty_columns);
    window_goto(status_window, 0, tty_columns - 1);
}


void
update_all()
{
    int i;

    tty_colors(ScreenBrightness, ScreenForeground, ScreenBackground);

    if (tty_lines <= 4)
	return;

    window_goto(processes_window, 0, 0);

    for (i = first_on_screen;
	 i < processes && (i - first_on_screen < tty_lines - 3); i++)
	    if (i != current_process)
		update_process(i, OFF);
	    else
		window_goto(processes_window, i - first_on_screen, 0);

    update_process(current_process, ON);

    tty_colors(ScreenBrightness, ScreenForeground, ScreenBackground);

    memset(global_buf, ' ', tty_columns);

    for (; i - first_on_screen < tty_lines - 3; i++)
    {
	window_goto(processes_window, i - first_on_screen, 0);
	window_puts(processes_window, global_buf, tty_columns);
    }

    window_goto(status_window, 0, tty_columns - 1);
}


void
clean_up()
{
    tty_end(NULL);
    remove_log();
}


void
fatal(postmsg)
    char *postmsg;
{
    clean_up();
    fprintf(stderr, "%s: fatal error: %s.\n", program, postmsg);
    exit(1);
}


int
ps(args)
    char **args;
{
    FILE *stdout_log, *stderr_log;

    /* See the comment in system.c on closing tty descriptors.  */
    int old_stdout = dup(1);
    int old_stderr = dup(2);

    close(1);
    close(2);

    stdout_log = fopen(stdout_log_name, "w");
    stderr_log = fopen(stderr_log_name, "w");

    if (ps_cmd)
	xfree(ps_cmd);

    if (args)
    {
	int i, bytes = 0;

	for (i = 0; args[i]; i++)
	    bytes += 1 + strlen(args[i]);

	ps_cmd = xmalloc(16 + bytes + 1);

	strcpy(ps_cmd, "ps");

	for (i = 0; args[i]; i++)
	{
	    strcat(ps_cmd, " ");
	    strcat(ps_cmd, args[i]);
	}
    }
    else
	ps_cmd = xstrdup("ps");

    if (system(ps_cmd) != 0)
    {
	fclose(stdout_log);
	fclose(stderr_log);

	dup(old_stdout);
	dup(old_stderr);

	close(old_stdout);
	close(old_stderr);

	fprintf(stderr, "%s: invalid command line for ps(1).\n", program);
	fprintf(stderr, "%s: the command was: `%s'.\n", program, ps_cmd);
	fprintf(stderr, "%s: see the ps(1) man page for details.\n", program);

	return 0;
    }

    fclose(stdout_log);
    fclose(stderr_log);

    dup(old_stdout);
    dup(old_stderr);

    close(old_stdout);
    close(old_stderr);

    return 1;
}


int
read_keys(keys)
    int keys;
{
    char *contents;
    char key_seq[80];
    int i, j, need_conversion;


    for (i = keys; i < MAX_KEYS; i++)
    {
	configuration_getvarinfo(key_seq, &contents, 1, NO_SEEK);

	if (*key_seq == 0)
	    break;

	if (*key_seq != '^')
	{
	    char *key_seq_ptr = tty_get_symbol_key_seq(key_seq);

	    if (key_seq_ptr)
	    {
		/* Ignore empty/invalid key sequences.  */
		if (*key_seq_ptr == '\0')
		    continue;

		/* We got the key sequence in the correct form, as
		   returned by tgetstr, so there is no need for
		   further conversion.  */
		strcpy(key_seq, key_seq_ptr);
		need_conversion = 0;
	    }
	    else
	    {
		/* This is not a TERMCAP symbol, it is a key sequence
		   that we will have to convert it with
		   tty_key_convert() into a machine usable form before
		   using it.  */
		need_conversion = 1;
	    }
	}
	else
	    need_conversion = 1;

	if (contents == NULL)
	    continue;

	for (j = 0; j < BUILTIN_OPERATIONS; j++)
	    if (strcmp(contents, built_in[j]) == 0)
		break;

	if (j < BUILTIN_OPERATIONS)
	{
	    if (!need_conversion || tty_key_convert((unsigned char *)key_seq))
		tty_key_list_insert((unsigned char *)key_seq, built_in[j]);
	}
	else
	    fprintf(stderr, "%s: invalid built-in operation: %s.\n",
		    program, contents);
    }

    return i;
}


void
resize(resize_required)
    int resize_required;
{
    int display_title = OFF;
    int display_header = OFF;
    int display_processes = OFF;
    int old_tty_lines = tty_lines;
    int old_tty_columns = tty_columns;

    tty_resize();

    /* Don't resize, unless absolutely necessary.  */
    if (!resize_required)
	if (tty_lines == old_tty_lines && tty_columns == old_tty_columns)
	    return;

#ifdef HAVE_LINUX
    if (LinuxConsole)
	screen = xrealloc(screen, 4 + tty_columns * tty_lines * 2);
#endif  /* HAVE_LINUX */

    /* Watch out for special cases (tty_lines < 7) because some
     * components will no longer fit.
     *
     * The title needs one line.  The panels need four.  There is one
     * more line for the input line and one for the status.
     *
     * Cases (lines available):
     *      1 line:	the status
     *      2 lines:	title + status
     *    3-4 lines:	title + header + status
     *	 >= 5 lines:	everything
     */

    global_buf = xrealloc(global_buf, tty_columns + 1);

    current_process = min(current_process, processes - 1);
    first_on_screen = max(0, current_process - (tty_lines - 3) + 1);
    assert(first_on_screen >= 0);

    window_resize(status_window, 0, tty_lines - 1, 1, tty_columns);

    if (tty_lines >= 2)
	display_title = ON;

    if (tty_lines >= 3)
	display_header = ON;

    if (tty_lines >= 5)
    {
	display_processes = ON;

	if (StartupScrollStep <= 0 || StartupScrollStep >= (tty_lines - 3) - 1)
	    scroll_step = (tty_lines - 3) / 2;
	else
	    scroll_step = StartupScrollStep;
    }

    window_resize(title_window, 0, 0, display_title ? 1 : 0, tty_columns);
    window_resize(header_window, 0, 1, display_header ? 1 : 0, tty_columns);
    window_resize(processes_window, 0, 2,
		  display_processes ? (tty_lines - 3) : 0, tty_columns);
}


/*
 * Resize (if necessary) and then refresh all gitps' components.
 */

void
refresh(signum)
    int signum;
{
    resize(0);

    if (signum == SIGCONT)
    {
	/* We were suspended.  Switch back to noncanonical mode.  */
	tty_set_mode(TTY_NONCANONIC);
	tty_defaults();
    }

    if (tty_lines == 4)
    {
	/* We know that when this is the case, the processes will not
	   be displayed and there will be an empty line that
	   potentially can contain some junk.  */
	tty_defaults();
	tty_clear();
    }

    set_title();
    set_header();
    set_status((char *)NULL);
    set_signal();
    update_all();
    tty_update();

    if (signum == SIGCONT)
	tty_update_title(ps_cmd);
}


void
hide()
{
    tty_set_mode(TTY_CANONIC);
    tty_defaults();
    tty_put_screen(screen);
}


void
clock_refresh()
{
}


void
usage()
{
    printf("usage: %s [-hvilcbp]\n", program);
    printf(" -h         print this help message\n");
    printf(" -v         print the version number\n");
    printf(" -i         print the installation directory\n");
    printf(" -c         use ANSI colors\n");
    printf(" -b         don't use ANSI colors\n");
    printf(" -l         don't use the last screen character\n");
    printf(" -p         pass the remaining arguments to ps(1)\n");
}


int
main(argc, argv)
    int argc;
    char *argv[];
{
    char *tmp;
    int key, keys;
    tty_key_t *ks;
    FILE *stdout_log;
    int repeat_count;
    char **arguments;
    int i, no_of_arguments, exit_code = 0, r1, r2;
    int need_update, need_update_all, old_current_process;
    int c, ansi_colors = -1, use_last_screen_character = ON;


    /* Make sure we don't get signals before we are ready to handle
       them.  */
    signals_init();

    program = argv[0];

    home = getenv("HOME");
    if (home == NULL)
	home = ".";

    compute_directories();
    get_login_name();

    if (getenv("COLORTERM") != NULL)
	ansi_colors = ON;

    /* Parse the command line.  */
    while ((c = getopt(argc, argv, "hvicblp")) != -1)
	switch (c)
	{
	    case 'h':
		/* Help request.  */
		usage();
		return 0;

	    case 'v':
		/* Version number request.  */
		printf("%s %s\n", PRODUCT, VERSION);
		return 0;

	    case 'i':
		/* Installation directory request.  */
		printf("%s is installed in %s.\n", PRODUCT, PREFIX);
		return 0;

	    case 'c':
		/* Force git to use ANSI color sequences.  */
		ansi_colors = ON;
		break;

	    case 'b':
		/* Prevent git from using ANSI color sequences.  */
		ansi_colors = OFF;
		break;

	    case 'l':
		/* Prevent git from using the last character on the
		   screen.  */
		use_last_screen_character = OFF;
		break;

	    case 'p':
		/* End the list of gitps arguments.  If this option is
		   given, the following arguments are to be passed to
		   `ps(1)'.  */
		goto done;

	    case '?':
		return 1;

	    default:
		fprintf(stderr, "%s: unknown error\n", program);
		return 1;
	}

  done:
    if (optind < argc)
    {
	no_of_arguments = argc - optind;
	arguments = (char **)xmalloc(sizeof(char *) * (no_of_arguments + 1));

	for (i = 0; i < no_of_arguments; i++)
	    arguments[i] = argv[optind++];

	arguments[i] = NULL;
    }
    else
	arguments = NULL;

    title_text = xmalloc(strlen(PRODUCT) + strlen(VERSION) + 64);
    sprintf(title_text, " %s %s - Process Viewer/Killer", PRODUCT, VERSION);

    tty_init(TTY_FULL_INPUT);

    common_configuration_init();
    use_section("[GITPS-Keys]");
    keys = read_keys(0);
    configuration_end();

    specific_configuration_init();
    use_section("[Setup]");

    temporary_directory = getenv("TMPDIR");

    if (temporary_directory == NULL)
	temporary_directory = "/tmp";

    if (ansi_colors == -1)
	AnsiColors = get_flag_var("AnsiColors", OFF);
    else
	AnsiColors = ansi_colors;

    if (use_last_screen_character)
	UseLastScreenChar = get_flag_var("UseLastScreenChar",  OFF);
    else
	UseLastScreenChar = OFF;

    tty_set_last_char_flag(UseLastScreenChar);

    StartupScrollStep  = get_int_var("StartupScrollStep", (tty_lines - 3) / 2);


    use_section("[GITPS-Setup]");

    help = get_string_var("Help", "");


    use_section(AnsiColors ? color_section : monochrome_section);

    get_colorset_var(PSColors, PSFields, PS_FIELDS);


    use_section("[GITPS-Keys]");

    keys = read_keys(keys);

    if (keys == MAX_KEYS)
	fprintf(stderr, "%s: too many key sequences; only %d are allowed.\n",
		program, MAX_KEYS);

    configuration_end();

#ifndef HAVE_LONG_FILE_NAMES
    fprintf(stderr,
	    "%s: warning: your system doesn't support long file names.",
	    program);
#endif /* !HAVE_LONG_FILE_NAMES */

    stdout_log_name = xmalloc(32 + strlen(temporary_directory) + 1);
    stderr_log_name = xmalloc(32 + strlen(temporary_directory) + 1);

    srand(time(NULL));
    r1 = 1 + (int) (100000000.0 * rand() / (RAND_MAX + 1.0));
    r2 = 1 + (int) (100000000.0 * rand() / (RAND_MAX + 1.0));

    sprintf(stdout_log_name, "%s/gitps.1.%d.%d",
	    temporary_directory, (int)getpid(), r1);
    sprintf(stderr_log_name, "%s/gitps.2.%d.%d",
	    temporary_directory, (int)getpid(), r2);

    if (ps(arguments) == 0)
	return 1;

    title_window  = window_init();
    header_window = window_init();
    processes_window = window_init();
    status_window = window_init();

    resize(0);

    tty_get_screen(screen);
    tty_set_mode(TTY_NONCANONIC);
    tty_defaults();

    signal_handlers(ON);

    first_on_screen = current_process = 0;

    tty_update_title(ps_cmd);

  restart:
    stdout_log = fopen(stdout_log_name, "r");
    remove_log();

    if ((PID_index = get_PID_index(stdout_log)) == -1)
    {
	exit_code = 1;
	goto end;
    }

    free_ps_list();
    build_ps_list(stdout_log);
    fclose(stdout_log);

    refresh(0);

    while (1)
    {
	ks  = tty_get_key(&repeat_count);
	key = ((char *)ks->aux_data - (char *)built_in) / MAX_BUILTIN_NAME;

	switch (key)
	{
	    case BUILTIN_previous_line:
		need_update_all = need_update = 0;

		while (repeat_count--)
		{
		    if (current_process != 0)
			current_process--;
		    else
			break;

		    if (current_process + 1 == first_on_screen)
		    {
			first_on_screen = max(0, first_on_screen -
					      scroll_step);
			need_update_all = 1;
		    }
		    else
		    {
			if (!need_update)
			    update_process(current_process + 1, ON);

			need_update = 1;
		    }
		}

		if (need_update_all)
		    update_all();
		else
		    if (need_update)
			update_process(current_process, ON);
		break;

	    case BUILTIN_next_line:
		need_update_all = need_update = 0;

		while (repeat_count--)
		{
		    if (current_process < processes - 1)
			current_process++;
		    else
			break;

		    if (current_process - first_on_screen >= tty_lines - 3)
		    {
			first_on_screen = min(first_on_screen +
					      scroll_step,
					      processes - 1 -
					      (tty_lines - 3) + 1);
			need_update_all = 1;
			continue;
		    }

		    if (!need_update)
			update_process(current_process - 1, ON);

		    need_update = 1;
		}

		if (need_update_all)
		    update_all();
		else
		    if (need_update)
			update_process(current_process, ON);
		break;

	    case BUILTIN_scroll_down:
		if (current_process == 0)
		    break;

		old_current_process = current_process;

		if (current_process < tty_lines - 3)
		    current_process = first_on_screen = 0;
		else
		{
		    current_process -= tty_lines - 3;
		    first_on_screen = max(0, first_on_screen - (tty_lines-3));
		}

		if (processes > tty_lines - 3)
		    update_all();
		else
		{
		    update_process(old_current_process, ON);
		    update_process(current_process, ON);
		}

		break;

	    case BUILTIN_scroll_up:
		if (current_process == processes - 1)
		    break;

		old_current_process = current_process;

		if (processes - 1 - first_on_screen < tty_lines - 3)
		    current_process = processes - 1;
		else
		    if (processes - 1 - current_process < tty_lines - 3)
		    {
			current_process = processes - 1;
			first_on_screen = processes - 1 - (tty_lines - 3) + 1;
		    }
		    else
		    {
			current_process += tty_lines - 3;
			first_on_screen = min(first_on_screen + tty_lines - 3,
					      (processes - 1) -
					      (tty_lines - 3) + 1);
		    }

		if (processes > tty_lines - 3)
		    update_all();
		else
		{
		    update_process(old_current_process, ON);
		    update_process(current_process, ON);
		}

		break;

	    case BUILTIN_beginning_of_list:
		if (current_process == 0)
		    break;

		current_process = first_on_screen = 0;
		update_all();
		break;

	    case BUILTIN_end_of_list:
		if (current_process == processes - 1)
		    break;

		current_process = processes - 1;
		first_on_screen = max(0, (processes-1) - (tty_lines-3) + 1);
		update_all();
		break;

	    case BUILTIN_next_signal:
		signal_type++;
		signal_type %= sizeof(sigdesc) / sizeof(xsignal_t);
		set_signal();
		break;

	    case BUILTIN_SIGHUP:  signal_type =  0; set_signal(); break;
	    case BUILTIN_SIGINT:  signal_type =  1; set_signal(); break;
	    case BUILTIN_SIGQUIT: signal_type =  2; set_signal(); break;
	    case BUILTIN_SIGILL:  signal_type =  3; set_signal(); break;
	    case BUILTIN_SIGFPE:  signal_type =  4; set_signal(); break;
	    case BUILTIN_SIGKILL: signal_type =  5; set_signal(); break;
	    case BUILTIN_SIGUSR1: signal_type =  6; set_signal(); break;
	    case BUILTIN_SIGSEGV: signal_type =  7; set_signal(); break;
	    case BUILTIN_SIGUSR2: signal_type =  8; set_signal(); break;
	    case BUILTIN_SIGPIPE: signal_type =  9; set_signal(); break;
	    case BUILTIN_SIGALRM: signal_type = 10; set_signal(); break;
	    case BUILTIN_SIGTERM: signal_type = 11; set_signal(); break;
	    case BUILTIN_SIGCHLD: signal_type = 12; set_signal(); break;
	    case BUILTIN_SIGCONT: signal_type = 13; set_signal(); break;

	    case BUILTIN_hard_refresh:
		tty_touch();

	    case BUILTIN_refresh:
		ps(arguments);
		goto restart;

	    case BUILTIN_exit:
		goto end;

	    case BUILTIN_kill_process:
		if (!kill_process(current_process))
		{
		    int e = errno;

		    tty_beep();
		    memset(global_buf, ' ', tty_columns);
		    tmp = xmalloc(16 + strlen((e == EPERM) ? no_perm:no_proc));
		    sprintf(tmp, "Error: %s", (e == EPERM) ? no_perm:no_proc);
		    set_status(tmp);
		    xfree(tmp);
		    errno = 0;
		    tty_get_key(NULL);
		    set_status((char *)NULL);
		    set_signal();
		}
		break;
	}
    }

  end:
    remove_log();
    tty_set_mode(TTY_CANONIC);
    tty_end(screen);
    return exit_code;
}
