/* Keyboard support routines.

   Copyright (C) 1994,1995 the Free Software Foundation.

   Written by: 1994, 1995 Miguel de Icaza.
               1994, 1995 Janne Kukonlehto.
	       1995  Jakub Jelinek.
   
   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif
#include <sys/types.h>		/* FD_ZERO et al */
#include <sys/time.h>		/* struct timeval */
#if IS_AIX
#   include <sys/select.h>
#endif
#include "tty.h"
#include <ctype.h>
#include <errno.h>
#include <malloc.h> 
#include "util.h"		/* For xmalloc prototype */
#include "mad.h"		/* The memory debugger */
#include "global.h"
#include "mouse.h"
#include "key.h"
#include "main.h"
#include "file.h"
#include "../vfs/vfs.h"
#ifdef __linux__
#  include <linux/termios.h> /* This is needed for TIOCLINUX */
#endif
#ifdef HAVE_TK
#   include <tcl.h>
#endif

/* "$Id: key.c,v 1.18 1995/02/21 19:06:13 miguel Exp $" */

/* This macros were stolen from gpm 0.15 */
#define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
#define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \
			 (t2.tv_usec-t1.tv_usec)/1000)
			 
/* timeout for old_esc_mode in usec */
#define ESCMODE_TIMEOUT 1000000

int mou_auto_repeat = 100;
int double_click_speed = 250;
int old_esc_mode = 0;

int use_8th_bit_as_meta = 1;

#ifdef HAVE_XVIEW
int xmouse_flag = 0;
#endif

#ifndef HAVE_XVIEW
typedef struct key_def {
    char ch;			/* Holds the matching char code */
    int code;			/* The code returned, valid if child == NULL */
    struct key_def *next;
    struct key_def *child;	/* sequence continuation */
    int action;			/* optional action to be done. Now used only
                                   to mark that we are just after the first
                                   Escape */
} key_def;

/* This holds all the key definitions */
static key_def *keys = 0;
#endif

static int input_fd;
static fd_set select_set;
static int disabled_channels = 0; /* Disable channels checking */

#ifndef HAVE_TK
/* File descriptor monitoring add/remove routines */
typedef struct SelectList {
    int fd;
    select_fn callback;
    void *info;
    struct SelectList *next;
} SelectList;

SelectList *select_list = 0;
#endif

int xgetch_second (void);

#ifdef HAVE_TK
void add_select_channel (int fd, select_fn callback, void *info)
{
    Tcl_File handle;

    handle = Tcl_GetFile ((ClientData) fd, TCL_UNIX_FD);
    Tcl_CreateFileHandler (handle, TCL_READABLE, (Tcl_FileProc *) callback, 0);
}

void delete_select_channel (int fd)
{
    Tcl_File handle;

    handle = Tcl_GetFile ((ClientData) fd, TCL_UNIX_FD);
    Tcl_DeleteFileHandler (handle);
    Tcl_FreeFile (handle);
}
#else
void add_select_channel (int fd, select_fn callback, void *info)
{
    SelectList *new;

    new = xmalloc (sizeof (SelectList), "add_select_channel");
    new->fd = fd;
    new->callback = callback;
    new->info = info;
    new->next = select_list;
    select_list = new;
}

void delete_select_channel (int fd)
{
    SelectList *p = select_list;
    SelectList *prev = 0;
    
    while (p){
	if (p->fd == fd){
	    if (prev)
		prev->next = p->next;
	    else
		select_list = p->next;
	    free (p);
	}
	prev = p;
    }
}

inline static int add_selects (fd_set *select_set)
{
    SelectList *p;
    int        top_fd = 0;

    if (disabled_channels)
	return 0;
    
    for (p = select_list; p; p = p->next){
	FD_SET (p->fd, select_set);
	if (p->fd > top_fd)
	    top_fd = p->fd;
    }
    return top_fd;
}

static void check_selects (fd_set *select_set)
{
    SelectList *p;

    if (disabled_channels)
	return;
    
    for (p = select_list; p; p = p->next)
	if (FD_ISSET (p->fd, select_set))
	    (*p->callback)(p->fd, p->info);
}
#endif

void channels_down (void)
{
    disabled_channels ++;
}

void channels_up (void)
{
    if (!disabled_channels)
	fprintf (stderr,
		 "Error: channels_up called with disabled_channels = 0\n");
    disabled_channels--;
}

typedef struct {
    int code;
    char *seq;
    int action;
} key_define_t;

#ifndef HAVE_XVIEW
key_define_t mc_bindings [] = {
    { KEY_END,    ESC_STR ">", MCKEY_NOACTION },
    { KEY_HOME,   ESC_STR "<", MCKEY_NOACTION },

#ifdef linux
    /* Incredible, but many Linuxes still have old databases */
    { KEY_IC,     ESC_STR "[2~", MCKEY_NOACTION },
#endif
    { 0, 0, MCKEY_NOACTION },
};

/* Broken terminfo and termcap databases on xterminals */
key_define_t xterm_key_defines [] = {
    { KEY_F(1),   ESC_STR "[11~", MCKEY_NOACTION },
    { KEY_F(2),   ESC_STR "[12~", MCKEY_NOACTION },
    { KEY_F(3),   ESC_STR "[13~", MCKEY_NOACTION },
    { KEY_F(4),   ESC_STR "[14~", MCKEY_NOACTION },
    { KEY_F(5),   ESC_STR "[15~", MCKEY_NOACTION },
    { KEY_F(6),   ESC_STR "[17~", MCKEY_NOACTION },
    { KEY_F(7),   ESC_STR "[18~", MCKEY_NOACTION },
    { KEY_F(8),   ESC_STR "[19~", MCKEY_NOACTION },
    { KEY_F(9),   ESC_STR "[20~", MCKEY_NOACTION },
    { KEY_F(10),  ESC_STR "[21~", MCKEY_NOACTION },
    { 0, 0, MCKEY_NOACTION },
};

key_define_t mc_default_keys [] = {
    { ESC_CHAR,	ESC_STR, MCKEY_ESCAPE },
    { ESC_CHAR, ESC_STR ESC_STR, MCKEY_NOACTION },
    { 0, 0, MCKEY_NOACTION },
};
#endif

void define_sequences (key_define_t *kd)
{
#ifndef HAVE_XVIEW
    int i;
    
    for (i = 0; kd [i].code; i++)
	define_sequence(kd [i].code, kd [i].seq, kd [i].action);
#endif	
}

void init_key (void)
{
#ifndef HAVE_XVIEW
    char *term = (char *) getenv ("TERM");
    
    /* This has to be the first define_sequence */
    /* So, we can assume that the first keys member has ESC */
    define_sequences (mc_default_keys);
    
    /* Terminfo on irix does not have some keys */
    if ((!strncmp (term, "iris-ansi", 9)) || (!strncmp (term, "xterm", 5)))
	define_sequences (xterm_key_defines);
    
    define_sequences (mc_bindings);
    
#ifdef HAVE_SLANG
    input_fd = fileno (stdin);
#endif
#endif /* !HAVE_XVIEW */
}

#ifndef HAVE_XVIEW
void xmouse_get_event (Gpm_Event *ev)
{
    int btn;
    static struct timeval tv1 = { 0, 0 }; /* Force first click as single */
    static struct timeval tv2;
    static int clicks;

    /* Decode Xterm mouse information to a GPM style event */

    /* Variable btn has following meaning: */
    /* 0 = btn1 dn, 1 = btn2 dn, 2 = btn3 dn, 3 = btn up */
    btn = xgetch () - 32;
    
    /* There seems to be no way of knowing which button was released */
    /* So we assume all the buttons were released */

    if (btn == 3){
        ev->type = GPM_UP | (GPM_SINGLE << clicks);
        ev->buttons = 0;
	GET_TIME (tv1);
	clicks = 0;
    } else {
        ev->type = GPM_DOWN;
	GET_TIME (tv2);
	if (tv1.tv_sec && (DIF_TIME (tv1,tv2) < double_click_speed)){
	    clicks++;
	    clicks %= 3;
	} else
	    clicks = 0;
	
        switch (btn) {
	case 0:
            ev->buttons |= GPM_B_LEFT;
            break;
	case 1:
            ev->buttons |= GPM_B_MIDDLE;
            break;
	case 2:
            ev->buttons |= GPM_B_RIGHT;
            break;
	default:
            /* Nothing */
            break;
        }
    }
    /* Coordinates are 33-based */
    /* Transform them to 1-based */
    ev->x = xgetch () - 32;
    ev->y = xgetch () - 32;
}

static key_def *create_sequence (char *seq, int code, int action)
{
    key_def *base, *p, *attach;

    for (base = attach = NULL; *seq; seq++){
	p = xmalloc (sizeof (key_def), "create_sequence");
	if (!base) base = p;
	if (attach) attach->child = p;
	
	p->ch   = *seq;
	p->code = code;
	p->child = p->next = NULL;
	if (!seq[1])
	    p->action = action;
	else
	    p->action = MCKEY_NOACTION;
	attach = p;
    }
    return base;
}

/* The maximum sequence length (32 + null terminator) */
static int seq_buffer [33];
static int *seq_append = 0;

static int push_char (int c)
{
    if (!seq_append)
	seq_append = seq_buffer;
    
    if (seq_append == &(seq_buffer [sizeof (seq_buffer)-2]))
	return 0;
    *(seq_append++) = c;
    *seq_append = 0;
    return 1;
}
#endif /* !HAVE_XVIEW */

void define_sequence (int code, char *seq, int action)
{
#ifndef HAVE_XVIEW
    key_def *base;

    if (strlen (seq) > sizeof (seq_buffer)-1)
	return;
    
    for (base = keys; (base != 0) && *seq; ){
	if (*seq == base->ch){
	    if (base->child == 0){
		if (*(seq+1)){
		    base->child = create_sequence (seq+1, code, action);
		    return;
		} else {
		    /* The sequence clashes */
		    return;
		}
	    } else {
		base = base->child;
		seq++;
	    }
	} else {
	    if (base->next)
		base = base->next;
	    else {
		base->next = create_sequence (seq, code, action);
		return;
	    }
	}
    }
    keys = create_sequence (seq, code, action);
#endif    
}

#ifndef HAVE_XVIEW
static int *pending_keys;
#endif

int correct_key_code (int c)
{
    /* This is needed on some OS that do not support ncurses and */
    /* do some magic after read()ing the data */
    if (c == '\r')
	return '\n';

#ifdef IS_AIX
    if (c == KEY_SCANCEL)
	return '\t';
#endif

    if (c == KEY_F(0))
	return KEY_F(10);
	
    switch (c) {
        case KEY_KP_ADD: c = alternate_plus_minus ? ALT('+') : '+'; break;
        case KEY_KP_SUBTRACT: c = alternate_plus_minus ? ALT('-') : '-'; break;
        case KEY_KP_MULTIPLY: c = alternate_plus_minus ? ALT('*') : '*'; break;
    }

    return c;
}

int get_key_code (int no_delay)
{
#ifndef HAVE_XVIEW
    int c;
    static key_def *this = NULL, *parent;
    static struct timeval esctime = { -1, -1 };
    static int lastnodelay = -1;
    
    if (no_delay != lastnodelay) {
        this = NULL;
        lastnodelay = no_delay;
    }

 pend_send:
    if (pending_keys){
	int d = *pending_keys++;
 check_pend:
	if (!*pending_keys){
	    pending_keys = 0;
	    seq_append = 0;
	}
	if (d == ESC_CHAR && pending_keys){
	    d = ALT(*pending_keys++);
	    goto check_pend;
	}
	if ((d & 0x80) && use_8th_bit_as_meta)
	    d = ALT(d & 0x7f);
	this = NULL;
	return correct_key_code (d);
    }

 nodelay_try_again:
    if (no_delay) {
#ifdef BUGGY_CURSES
        wtimeout(stdscr, 500);
#else
        nodelay (stdscr, TRUE);
#endif
    }
    c = xgetch ();
    if (no_delay) {
#ifdef BUGGY_CURSES
        notimeout (stdscr, TRUE);
#else
        nodelay (stdscr, FALSE);
#endif
        if (c == ERR) {
            if (this != NULL && parent != NULL && 
                parent->action == MCKEY_ESCAPE && old_esc_mode) {
                struct timeval current, timeout;
                
                if (esctime.tv_sec == -1)
                    return ERR;
                GET_TIME (current);
                timeout.tv_sec = ESCMODE_TIMEOUT / 1000000 + esctime.tv_sec;
                timeout.tv_usec = ESCMODE_TIMEOUT % 1000000 + esctime.tv_usec;
                if (timeout.tv_usec > 1000000) {
                    timeout.tv_usec -= 1000000;
                    timeout.tv_sec++;
                }
                if (current.tv_sec < timeout.tv_sec)
                    return ERR;
                if (current.tv_sec == timeout.tv_sec && 
                    current.tv_usec < timeout.tv_usec)
                    return ERR;
                this = NULL;
		pending_keys = seq_append = NULL;
		return ESC_CHAR;
            }
            return ERR;
        }
    } else if (c == ERR){
	/* Maybe we got an incomplete match.
	   This we do only in delay mode, since otherwise
	   xgetch can return ERR at any time. */
	if (seq_append) {
	    pending_keys = seq_buffer;
	    goto pend_send;
	}
	this = NULL;
	return ERR;
    }
    
    /* Search the key on the root */
    if (!no_delay || this == NULL) {
        this = keys;
        parent = NULL;

        if ((c & 0x80) && use_8th_bit_as_meta) {
            c &= ~0x7f;

	    /* The first sequence defined starts with esc */
	    parent = keys;
	    this = keys->child;
        }
    }
    while (this){
	if (c == this->ch){
	    if (this->child){
		if (!push_char (c)){
		    pending_keys = seq_buffer;
		    goto pend_send;
		}
		parent = this;
		this = this->child;
		if (parent->action == MCKEY_ESCAPE && old_esc_mode) {
		    if (no_delay) {
		        GET_TIME (esctime);
		        if (this == NULL) {
		            /* Shouldn't happen */
		            fprintf (stderr, "Internal error\n");
		            exit (1);
		        }
		        goto nodelay_try_again;
		    }
		    esctime.tv_sec = -1;
		    c = xgetch_second ();
		    if (c == ERR) {
		        pending_keys = seq_append = NULL;
		        this = NULL;
		        return ESC_CHAR;
		    }
		} else {
		    if (no_delay)
		        goto nodelay_try_again;
		    c = xgetch ();
		}
	    } else {
		/* We got a complete match, return and reset search */
		int code;
		
		pending_keys = seq_append = NULL;
		code = this->code;
		this = NULL;
		return correct_key_code (code);
	    }
	} else {
	    if (this->next)
		this = this->next;
	    else {
	        if (parent != NULL && parent->action == MCKEY_ESCAPE) {
	            /* This is just to save a lot of define_sequences */
	            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
			|| (c == '\n') || (c == '\t') || (c == XCTRL('h'))
			|| (c == KEY_BACKSPACE) || (c == '!') || (c == '\r')
			|| c == 127 || c == '+' || c == '-' || c == '\\' 
			|| c == '?')
			c = ALT(c);
		    else if (isdigit(c))
	                c = KEY_F (c-'0');
		    else if (c == ' ')
			c = ESC_CHAR;
		    pending_keys = seq_append = NULL;
		    this = NULL;
		    return correct_key_code (c);
		}
		/* Did not find a match or {c} was changed in the if above,
		   so we have to return everything we had skipped
		 */
		push_char (c);
		pending_keys = seq_buffer;
		goto pend_send;
	    }
	}
    }
    this = NULL;
    return correct_key_code (c);
#else
    return ERR;
#endif /* HAVE_XVIEW */
}

#ifndef HAVE_TK
/* If set timeout is set, then we wait 0.1 seconds, else, we block */
void try_channels (int set_timeout)
{
    struct timeval timeout;
    static fd_set select_set;
    struct timeval *timeptr;
    int v;

    while (1){
	FD_ZERO (&select_set);
	FD_SET  (input_fd, &select_set);	/* Add stdin */
	add_selects (&select_set);
	
	if (set_timeout){
	    timeout.tv_sec = 0;
	    timeout.tv_usec = 100000;
	    timeptr = &timeout;
	} else
	    timeptr = 0;

	v = select (FD_SETSIZE, &select_set, NULL, NULL, timeptr);
	if (v > 0){
	    check_selects (&select_set);
	    if (FD_ISSET (input_fd, &select_set))
		return;
	}
    }
}

#ifndef HAVE_XVIEW
/* Workaround for System V Curses vt100 bug */
static int getch_with_delay (void)
{
    int c;

    /* This routine could be used on systems without mouse support,
       so we need to do the select check :-( */
    while (1){
	if (!pending_keys)
	    try_channels (0);

	/* Try to get a character */
	c = get_key_code (0);
	if (c != ERR)
	    break;
	/* Failed -> wait 0.1 secs and try again */
	try_channels (1);
    }
    /* Success -> return the character */
    return c;
}
#endif

#ifndef HAVE_LIBGPM
#define gpm_flag 0
#endif
#endif /* !HAVE_XVIEW */

extern int max_dirt_limit;

/* Returns a character read from stdin with appropriate interpretation */
/* Also takes care of generated mouse events */
/* Returns 0 if it is a mouse event */
/* The current behavior is to block allways */
int get_event (Gpm_Event *event, int redo_event)
{
#ifndef HAVE_X
#define block 1
    int c;
    static int flag;			/* Return value from select */
#ifdef HAVE_LIBGPM
    static Gpm_Event ev;		/* Mouse event */
#endif
    struct timeval timeout;
    struct timeval *time_addr = NULL;
    static int dirty = 3;

    if ((dirty == 3) || is_idle ()){
	refresh ();
	doupdate ();
	dirty = 1;
    } else
	dirty++;

    vfs_timeout_handler ();
    
    /* Ok, we use (event->x < 0) to signal that the event does not contain
       a suitable position for the mouse, so we can't use show_mouse_pointer
       on it.
    */
    if (event->x > 0){
	show_mouse_pointer (event->x, event->y);
	if (!redo_event)
	    event->x = -1;
    }

    /* Repeat if using mouse */
    while ((xmouse_flag || gpm_flag) && !pending_keys)
    {
	if (xmouse_flag || gpm_flag)
	{
	    FD_ZERO (&select_set);
	    FD_SET  (input_fd, &select_set);
	    add_selects (&select_set);

#ifdef HAVE_LIBGPM
	    if (gpm_flag) {
		FD_SET  (gpm_fd, &select_set);
	    }
#endif

	    if (redo_event){
	        timeout.tv_usec = mou_auto_repeat * 1000;
		timeout.tv_sec = 0;

		time_addr = &timeout;
	    } else {
		int seconds;
		
		if ((seconds = vfs_timeouts ())){
		    /* the timeout could be improved and actually be
		     * the number of seconds until the next vfs entry
		     * timeouts in the stamp list.
		     */

		    timeout.tv_sec = seconds;
		    timeout.tv_sec = 0;
		    time_addr = &timeout;
		} else
		    time_addr = NULL;
	    }

	    if (!block){
		time_addr = &timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = 0;
	    }
	    enable_interrupt_key ();
	    flag = select (FD_SETSIZE, &select_set, NULL, NULL, time_addr);
	    disable_interrupt_key ();
	    
	    /* select timed out: it could be for any of the following reasons:
	     * redo_event -> it was because of the MOU_REPEAT handler
	     * !block     -> we did not block in the select call
	     *               (this is currently unused).
	     * else       -> 10 second timeout to check the vfs status.
	     */
	    if (flag == 0){
		if (redo_event)
		    return 0;
		if (!block)
		    return -1;
		vfs_timeout_handler ();
	    }
	    if (flag == -1 && errno == EINTR)
		return -1;

	    check_selects (&select_set);
	    
	    if (FD_ISSET (input_fd, &select_set))
	        break;
	}
#ifdef HAVE_LIBGPM
	if (gpm_flag && FD_ISSET (gpm_fd, &select_set)){
	    if (gpm_flag){
		Gpm_GetEvent (&ev);
		Gpm_FitEvent (&ev);
	    }
	    *event = ev;
	    return 0;
	}
#endif
    }
#   ifndef HAVE_SLANG
    flag = is_wintouched(stdscr);
    untouchwin (stdscr);
#   endif

    c = getch_with_delay ();
    
#   ifndef HAVE_SLANG
    if (flag)
        touchwin (stdscr);
#   endif
    
    if (!c) { /* Mouse event */
        xmouse_get_event (event);
        return 0;
    }

    return c;
#else
    return -1;
#endif /* HAVE_XVIEW */
}

/* Returns a key press, mouse events are discarded */
int mi_getch ()
{
#ifdef HAVE_X
#ifdef HAVE_TK
    return tk_getch ();
#else
    /* FIXME: XView requires this routine to work with quote */
#endif /* HAVE_TK */
#else
    Gpm_Event ev;
    int       key;
    
    while ((key = get_event (&ev, 0)) == 0)
	;
    return key;
#endif /* HAVE_X */
}

int xgetch_second (void)
{
    fd_set Read_FD_Set;
    int c;
    struct timeval timeout;

    timeout.tv_sec = ESCMODE_TIMEOUT / 1000000;
    timeout.tv_usec = ESCMODE_TIMEOUT % 1000000;
#ifdef BUGGY_CURSES
    wtimeout(stdscr, 500);
#else
    nodelay (stdscr, TRUE);
#endif
    FD_ZERO (&Read_FD_Set);
    FD_SET (input_fd, &Read_FD_Set);
    select (FD_SETSIZE, &Read_FD_Set, NULL, NULL, &timeout);
    c = xgetch ();
#ifdef BUGGY_CURSES
    notimeout (stdscr, TRUE);
#else
    nodelay (stdscr, FALSE);
#endif
    return c;
}

#ifndef HAVE_X
void learn_store_key (char *buffer, char **p, int c)
{
    if (*p - buffer > 253)
        return;
    if (c == ESC_CHAR) {
        *(*p)++ = '\\';
        *(*p)++ = 'e';
    } else if (c < ' ') {
    	*(*p)++ = '^';
    	*(*p)++ = c + 'a' - 1;
    } else if (c == '^') {
    	*(*p)++ = '^';
    	*(*p)++ = '^';
    } else
    	*(*p)++ = (char) c;
}

char *learn_key (void)
{
/* LEARN_TIMEOUT in usec */
#define LEARN_TIMEOUT 200000

    fd_set Read_FD_Set;
    struct timeval endtime;
    struct timeval timeout;
    int c = xgetch ();
    char buffer [256];
    char *p = buffer;
    
    while (c == ERR)
        c = xgetch (); /* Sanity check, should be unnecessary */
    learn_store_key (buffer, &p, c);
    GET_TIME (endtime);
    endtime.tv_usec += LEARN_TIMEOUT;
    if (endtime.tv_usec > 1000000) {
        endtime.tv_usec -= 1000000;
        endtime.tv_sec++;
    }
#ifdef BUGGY_CURSES
    wtimeout(stdscr, 500);
#else
    nodelay (stdscr, TRUE);
#endif
    for (;;) {
        while ((c = xgetch ()) == ERR) {
            GET_TIME (timeout);
            timeout.tv_usec = endtime.tv_usec - timeout.tv_usec;
            if (timeout.tv_usec < 0)
                timeout.tv_sec++;
            timeout.tv_sec = endtime.tv_sec - timeout.tv_sec;
            if (timeout.tv_sec >= 0 && timeout.tv_usec > 0) {
    		FD_ZERO (&Read_FD_Set);
    		FD_SET (input_fd, &Read_FD_Set);
                select (FD_SETSIZE, &Read_FD_Set, NULL, NULL, &timeout);
            } else
            	break;
        }
        if (c == ERR)
            break;
	learn_store_key (buffer, &p, c);        
    }
#ifdef BUGGY_CURSES
    notimeout (stdscr, TRUE);
#else
    nodelay (stdscr, FALSE);
#endif
    *p = 0;
    return strdup (buffer);
}
#endif /* !HAVE_X */

/* A function to check if we're idle.
   Currently checks only for key presses.
   We could also check the mouse. */
int is_idle (void)
{
    /* Check for incoming key presses     *
     * If there are any we say we're busy */

    struct fd_set select_set;
    struct timeval timeout;
    FD_ZERO (&select_set);
    FD_SET (0, &select_set);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    select (FD_SETSIZE, &select_set, 0, 0, &timeout);
    return ! FD_ISSET (0, &select_set);
}

int ctrl_pressed ()
{
#ifdef __linux__
    int modifiers;

    modifiers = 6;

    if (ioctl (0, TIOCLINUX, &modifiers) < 0)
	return 0;

    if (modifiers & 4)
	return 1;
#endif
    return 0;
}

#ifdef HAVE_MAD
#ifndef HAVE_XVIEW
void k_dispose (key_def *k)
{
    if (!k)
	return;
    k_dispose (k->child);
    k_dispose (k->next);
    free (k);
}

void s_dispose (SelectList *sel)
{
    if (!sel)
	return;

    s_dispose (sel->next);
    free (sel);
}

void done_key ()
{
    k_dispose (keys);
    s_dispose (select_list);
}

#else

void done_key () 
{
}

#endif /* HAVE_XVIEW */
#endif /* HAVE_MAD */
