/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
/*LINTLIBRARY*/
#include "curses_inc.h"
#include <signal.h>
#include <unistd.h>
#ifdef DEBUG
#include <ctype.h>
#endif /* DEBUG */
/*
* Read a key typed from the terminal
*
* interpret: = 0 for single-char key only
* = 1 for matching function key and macro patterns.
* = 2 same as 1 but no time-out for funckey matching.
*/
static int _getkey(int, chtype *);
static int _fpk(void);
static int _pk(void);
chtype
tgetch(int interpret)
{
int i = 0, j, collapse = 1;
#define WAIT3 333
chtype inp;
chtype *inputQ = cur_term->_input_queue;
char *chars_onQ = &(cur_term->_chars_on_queue);
#ifdef SYSV
/*
* Register the fact that getch is being used so
* that typeahead checking can be done.
* This code should GO AWAY when a poll() or FIONREAD can
* be done on the file descriptor as then the check
* will be non-destructive.
*/
cur_term->fl_typeahdok = TRUE;
#endif /* SYSV */
/* ask for input */
if (cur_term->_ungotten > 0) {
cur_term->_ungotten--;
/* decode an ungetch()'d character */
inp = -inputQ[0];
} else {
/* Only read a character if there is no typeahead/peekahead. */
if (*chars_onQ == 0) {
/* (*chars_onQ)++; MR */
#ifdef FIONREAD
inp = _readchar();
#else /* FIONREAD */
inp = (chtype) _pk();
if ((int)inp == ERR) {
/*
* interpret is set to 0 so that down below we don't
* drop into getkey since we already know there can't be
* a key that starts with -1. Also, we don't want to
* access funckeystarter[-1].
*/
interpret = FALSE;
}
#endif /* FIONREAD */
(*chars_onQ)++;
} else
inp = inputQ[0];
#ifdef DEBUG
if (outf)
fprintf(outf, "TGETCH read '%s'\n", unctrl(inp));
#endif /* DEBUG */
/* Check for arrow and function keys */
if (interpret && cur_term->funckeystarter[inp])
collapse = _getkey(interpret - 1, &inp);
}
/* Collapse the input queue to remove the escape */
/* sequence from the stack. */
j = *chars_onQ;
(*chars_onQ) -= collapse;
while (collapse < j)
inputQ[i++] = inputQ[collapse++];
return (inp);
}
#ifdef FIONREAD
static int
_readchar()
{
int i;
unsigned char c;
if (cur_term->_delay == 0) {
int arg;
(void) ioctl(cur_term->_inputfd, FIONREAD, &arg);
#ifdef DEBUG
if (outf)
fprintf(outf, "FIONREAD returns %d\n", arg);
#endif /* DEBUG */
if (arg < 1)
return (-1);
} else
if (cur_term->_delay > 0) {
char c;
int infd;
infd = 1 << cur_term->_inputfd;
t.tv_sec = cur_term->_delay / 1000;
t.tv_usec = (cur_term->_delay % 1000) * 1000;
i = select(20, &infd, (int *)NULL, (int *)NULL, &t);
if (i < 0)
return (ERR);
i = read(cur_term->_inputfd, &c, 1);
} else
i = read(cur_term->_inputfd, &c, 1);
#ifdef DEBUG
if (outf)
fprintf(outf, "read from %d returns %d chars, first %o\n",
cur_term->_inputfd, i, c);
#endif /* DEBUG */
if (i > 0)
return (c);
else
return (ERR);
}
#endif /* !FIONREAD */
#ifdef DEBUG
extern char *_asciify();
#endif /* DEBUG */
static int get_xterm_mouse(int, int *);
static void _map_button(chtype *);
/*
* This algorithm is a "learning" algorithm. The premise is
* that keys used once are like to be used again and again.
* Since the time for a linear search of the table is so
* expensive, we move keys that are found up to the top of
* the list, making the access to a repeated key very fast and
* keys that have been used before close to the top.
*/
static int
_getkey(int blockpeek, chtype *inp)
{
_KEY_MAP **kp = cur_term->_keys;
int key, num_keys = cur_term->_ksz;
int i;
chtype *inputQ = cur_term->_input_queue;
char *chars_onQ = &(cur_term->_chars_on_queue);
char flag = cur_term->funckeystarter[*inp];
int first, collapse = 1;
#ifdef DEBUG
if (outf)
fprintf(outf, "getkey(): looking in linear table, "
"inp=%d\n", *inp);
#endif /* DEBUG */
if (flag & _KEY)
key = 0;
else {
key = cur_term->_first_macro;
blockpeek = TRUE;
}
first = key;
for (; key < num_keys; key++) {
if (kp[key]->_sends[0] == *inp) {
for (i = 1; i < INP_QSIZE; i++) {
/* found it? */
if (kp[key]->_sends[i] == '\0')
break;
/* partial match? peek ahead. */
if (*chars_onQ == i) {
(*chars_onQ)++;
inputQ[i] = (blockpeek) ?
_pk() : _fpk();
switch ((int)inputQ[i]) {
case -2:
/*
* Since -2 signifies a timeout we don't really
* want to put it on the queue so we decrement
* our counter.
*/
(*chars_onQ)--;
#ifdef DEBUG
if (outf)
fprintf(outf, "Timed out\n");
#endif /* DEBUG */
if (flag & _MACRO) {
#ifdef DEBUG
if (outf)
fprintf(outf,
"Found macro\n");
#endif /* DEBUG */
/*
* We have to decrement one because key will be
* incremented at the bottom of the out loop.
*/
key = (first = blockpeek =
cur_term->_first_macro) -
1;
goto outerloop;
}
/*FALLTHROUGH*/
case -1:
goto ret;
}
}
/* not this one? */
if (kp[key]->_sends[i] != inputQ[i])
goto outerloop;
}
/* SS-mouse */
if (kp[key]->_keyval == KEY_MOUSE) {
MOUSE_STATUS old_mouse;
int rc;
old_mouse = Mouse_status;
/* read the mouse status information */
if (mouse_info)
rc = -3; /* NOT IMPLEMENTED */
else
rc = get_xterm_mouse(blockpeek, &i);
if (rc == -1) /* read error */
goto ret;
else if (rc == -2 || rc == -3) /* timeout */
/* or not mouse */
goto outerloop;
else if (rc == 0) /* report mouse pos */
Mouse_status.changes |= 020;
else if (rc >= 1 && rc <= 3)
/* mouse button event */
Mouse_status.changes =
(((MOUSE_X_POS != old_mouse.x ||
MOUSE_Y_POS != old_mouse.y) << 3) |
((Mouse_status.button[2] !=
old_mouse.button[2]) << 2) |
((Mouse_status.button[1] !=
old_mouse.button[1]) << 1) |
(Mouse_status.button[0] !=
old_mouse.button[0]));
}
/* We found it! Read in any chars left in _sends */
if ((collapse = i) == INP_QSIZE)
for (; kp[key]->_sends[i]; i++)
(void) _fpk();
/* move key to top of ordered list */
if (key != first) {
_KEY_MAP *savekey = kp[key];
short *lorder;
int j;
if (key > cur_term->_first_macro)
lorder = &(cur_term->_lastmacro_ordered);
else
lorder = &(cur_term->_lastkey_ordered);
/*
* If we're below the last ordered key, swap next unordered
* key with this one and ripple from there.
*/
if (key > *lorder)
kp[key] = kp[(i = ++(*lorder))];
else
i = key;
/* ripple the ordered keys down */
for (j = i--; j > first; )
kp[j--] = kp[i--];
kp[first] = savekey;
}
*inp = kp[first]->_keyval;
/*
* SS-mouse support: if mouse button event
* occured on top of the soft label, we may
* have to return the function key corresponding
* to that soft label
*/
if (*inp == KEY_MOUSE && A_BUTTON_CHANGED &&
(MOUSE_Y_POS == LINES) &&
(SP->slk != (SLK_MAP *) NULL) &&
(SP->_map_mbe_to_key != 0)) {
_map_button(inp);
}
goto ret;
}
outerloop:
;
}
ret:
/* key not found */
#ifdef DEBUG
if (outf)
if (key == num_keys)
fprintf(outf, "Did not match anything.\n");
#endif /* DEBUG */
return (collapse);
}
/* SS-mouse */
/* this function tries to read in information that follows KEY_MOUSE: */
/* the first character identifies what button is involved (1,2,or 3) */
/* if the first character is 0, we are dealing with report_mouse_pos */
/*
* The routine returns the following:
* -3: not a mouse button event
* -2: read timed out
* -1: the read failed
* [0, 1, 2, 3] - the first character in the mouse event
*/
static int
get_xterm_mouse(int blockpeek, int *i)
{
chtype *inputQ = cur_term->_input_queue; /* ??? */
/* LINTED */
chtype *chars_onQ = (chtype *) &(cur_term->_chars_on_queue);
int j, mx, my;
int char1, char2, c1, c2;
/* the first character should be 0, 1, 2, or 4 */
char1 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
/* read error or timeout */
if (char1 < 0)
return (char1);
(*chars_onQ)++;
if (char1 < '0' || char1 > '3')
return (-3);
/* if the character is 1, 2, or 3 it must be followed by */
/* P, R, C, D, or T */
if (char1 != '0') {
char2 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
if (char2 < 0)
return (char2);
(*chars_onQ)++;
if (char2 != 'P' && char2 != 'R' && char2 != 'C' &&
char2 != 'D' && char2 != 'T')
return (-3);
}
/* read X and Y coordinates of the mouse */
for (j = 0; j < 2; j++) {
c1 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
if (c1 < 0)
return (c1);
(*chars_onQ)++;
if (c1 >= ' ' && c1 <= '~') { /* ascii char */
if (j == 0)
mx = c1 - ' ';
else
my = c1 - ' ';
} else if (char1 == 01 || char1 == 02) { /* ^A || ^B */
c2 = (inputQ[(*i)++] = (blockpeek) ? _pk() : _fpk());
if (c2 < 0)
return (c2);
(*chars_onQ)++;
if (c2 >= ' ' && c2 <= '~') {
if (j == 0)
mx = c1 * (c2 - ' ');
else
my = c1 * (c2 - ' ');
} else
return (-3);
} else
return (-3);
}
/* read complete mouse event: update the Mouse_status structure */
MOUSE_X_POS = mx;
MOUSE_Y_POS = my;
j = char1 - '0';
if (j != 0) {
switch (char2) {
case 'P':
BUTTON_STATUS(j) = BUTTON_PRESSED;
break;
case 'R':
BUTTON_STATUS(j) = BUTTON_RELEASED;
break;
case 'C':
BUTTON_STATUS(j) = BUTTON_CLICKED;
break;
case 'D':
BUTTON_STATUS(j) = BUTTON_DOUBLE_CLICKED;
break;
case 'T':
BUTTON_STATUS(j) = BUTTON_TRIPLE_CLICKED;
break;
}
}
return (j);
}
/* SS-mouse-end */
/*
* Fast peek key. Like getchar but if the right flags are set, times out
* quickly if there is nothing waiting, returning -1.
* f is an output stdio descriptor, we read from the fileno.
* We wait for long enough for a terminal to send another character
* (at 15cps repeat rate, this is 67 ms, I'm using 100ms to allow
* a bit of a fudge factor) and time out more quickly.
* -2 is returned if we time out, -1 is returned if interrupted, and the
* character is returned otherwise.
*/
#ifndef FIONREAD
/*
* Traditional implementation. The best resolution we have is 1 second,
* so we set a 1 second alarm and try to read. If we fail for 1 second,
* we assume there is no key waiting. Problem here is that 1 second is
* too long; people can type faster than this.
*
* Another possible implementation of changing VMIN/VTIME before and
* after each read does not work because the tty driver's timeout
* mechanism is too unreliable when the timeouts are changed too quickly.
*/
static char sig_caught;
static
#ifdef SIGPOLL /* Vr3 and beyond */
void
#endif /* SIGPOLL */
/* The following line causes a lint warning for "dummy" which is not used. */
_catch_alarm(int dummy)
{
sig_caught = 1;
}
static int
_fpk(void)
{
unsigned char c;
int infd = cur_term->_inputfd;
ssize_t rc;
#ifdef SIGPOLL /* Vr3 and beyond */
void (*oldsig)(int);
#else /* SIGPOLL */
int (*oldsig)(int);
#endif /* SIGPOLL */
unsigned int oldalarm, alarm(unsigned);
/* turn off any user alarms and set our own */
oldalarm = alarm(0);
sig_caught = 0;
oldsig = signal(SIGALRM, _catch_alarm);
(void) alarm(1);
rc = read(cur_term->_inputfd, (char *)&c, 1);
(void) alarm(0);
/*
* This code is to take care of the possibility of
* the process getting swapped out in the middle of
* read() call above. The interrupt will cause the
* read() call to retur, even if a character is really
* on the clist. So we do a non-blocking read() to make
* sure that there really isn't a character there.
*/
if (sig_caught && rc != 1)
if (cur_term->_check_fd != -1)
rc = read(cur_term->_check_fd, (char *)&c, 1);
else {
#include <fcntl.h>
int fcflags = fcntl(infd, F_GETFL, 0);
(void) fcntl(infd, F_SETFL, fcflags | O_NDELAY);
rc = read(infd, (char *)&c, 1);
(void) fcntl(infd, F_SETFL, fcflags);
}
/* restore the user alarms */
(void) signal(SIGALRM, oldsig);
if (sig_caught && oldalarm > 1)
oldalarm--;
(void) alarm(oldalarm);
if (rc == 1) /* got a character */
return (c);
else
if (sig_caught) /* timed out */
return (-2);
else /* EOF or got interrupted */
return (-1);
}
#else /* FIONREAD */
/*
* If we have the select system call, we can do much better than the
* traditional method. Even if we don't have the real 4.2BSD select, we
* can emulate it with napms and FIONREAD. napms might be done with only
* 1 second resolution, but this is no worse than what we have in the
* traditional implementation.
*/
static
_fpk()
{
int infd, rc;
int *outfd, *exfd;
unsigned char c;
struct timeval t;
infd = 1 << cur_term->_inputfd;
outfd = exfd = (int *)NULL;
t.tv_sec = 0;
t.tv_usec = 100000; /* 100 milliseconds */
rc = select(20, &infd, outfd, exfd, &t);
if (rc < 0)
return (-2);
rc = read(fileno(f), &c, 1);
return (rc == 1 ? c : -1);
}
#endif /* FIONREAD */
/*
* Plain peekchar function. Nothing fancy. This is just like _fpk
* but will wait forever rather than time out.
*/
static int
_pk(void)
{
unsigned char c;
return ((read(cur_term->_inputfd, (char *)&c, 1) == 1) ? c : ERR);
}
/*
* SS-mouse: check if this mouse button event should map into
* function key
*/
static void
_map_button(chtype *inp)
{
SLK_MAP *slk = SP->slk;
int num = slk->_num;
int len = slk->_len;
int i;
/* first determine if this mouse button event should be */
/* mapped into function key */
if (!(SP->_map_mbe_to_key &
((BUTTON_CHANGED(3) << (10 + BUTTON_STATUS(3))) |
(BUTTON_CHANGED(2) << (5 + BUTTON_STATUS(2))) |
(BUTTON_CHANGED(1) << BUTTON_STATUS(1)))))
return;
for (i = 0; i < num; i++) {
if (MOUSE_X_POS < slk->_labx[i])
break;
if (MOUSE_X_POS > slk->_labx[i] + len)
continue;
*inp = KEY_F(1) + i;
break;
}
}