/*
*
* liblow.c - client library - low level (gpm)
*
* Copyright 1994,1995 rubini@linux.it (Alessandro Rubini)
* Copyright (C) 1998 Ian Zimmerman <itz@rahul.net>
* Copyright 2001-2008 Nico Schottelius (nico-gpm2008 at schottelius.org)
*
* xterm management is mostly by jtklehto@stekt.oulu.fi (Janne Kukonlehto)
*
* 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.
********/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h> /* strncmp */
#include <unistd.h> /* select(); */
#include <errno.h>
#include <sys/time.h> /* timeval */
#include <sys/types.h> /* socket() */
#include <sys/socket.h> /* socket() */
#include <sys/un.h> /* struct sockaddr_un */
#include <sys/fcntl.h> /* O_RDONLY */
#include <sys/stat.h> /* stat() */
#ifdef SIGTSTP /* true if BSD system */
#include <sys/file.h>
#include <sys/ioctl.h>
#endif
#include <signal.h>
#include <linux/vt.h> /* VT_GETSTATE */
#include <sys/kd.h> /* KDGETMODE */
#include <termios.h> /* winsize */
#include "headers/gpmInt.h"
#include "headers/message.h"
#ifndef min
#define min(a,b) ((a)<(b) ? (a) : (b))
#define max(a,b) ((a)>(b) ? (a) : (b))
#endif
/*....................................... Stack struct */
typedef struct Gpm_Stst {
Gpm_Connect info;
struct Gpm_Stst *next;
} Gpm_Stst;
/*....................................... Global variables */
int gpm_flag=0; /* almost unuseful now -- where was it used for ? can
we remove it now ? FIXME */
int gpm_tried=0;
int gpm_fd=-1;
int gpm_hflag=0;
Gpm_Stst *gpm_stack=NULL;
struct timeval gpm_timeout={10,0};
Gpm_Handler *gpm_handler=NULL;
void *gpm_data=NULL;
int gpm_zerobased=0;
int gpm_visiblepointer=0;
int gpm_mx, gpm_my; /* max x and y (1-based) to fit margins */
unsigned char _gpm_buf[6*sizeof(short)];
unsigned short * _gpm_arg = (unsigned short *)_gpm_buf +1;
int gpm_consolefd=-1; /* used to invoke ioctl() */
int gpm_morekeys=0;
int gpm_convert_event(unsigned char *mdata, Gpm_Event *ePtr);
/*----------------------------------------------------------------------------*
* nice description
*----------------------------------------------------------------------------*/
static inline int putdata(int where, Gpm_Connect *what)
{
#ifdef GPM_USE_MAGIC
static int magic=GPM_MAGIC;
if (write(where,&magic,sizeof(int))!=sizeof(int)) {
gpm_report(GPM_PR_ERR,GPM_MESS_WRITE_ERR,strerror(errno));
return -1;
}
#endif
if (write(where,what,sizeof(Gpm_Connect))!=sizeof(Gpm_Connect))
{
gpm_report(GPM_PR_ERR,GPM_MESS_WRITE_ERR,strerror(errno));
return -1;
}
return 0;
}
#if (defined(SIGWINCH))
/* itz Wed Mar 18 11:19:52 PST 1998 hook window change as well */
/* Old SIGWINCH handler. */
static struct sigaction gpm_saved_winch_hook;
/*----------------------------------------------------------------------------*
* nice description
*----------------------------------------------------------------------------*/
static void gpm_winch_hook (int signum)
{
struct winsize win;
if (SIG_IGN != gpm_saved_winch_hook.sa_handler &&
SIG_DFL != gpm_saved_winch_hook.sa_handler) {
gpm_saved_winch_hook.sa_handler(signum);
} /*if*/
if (ioctl(gpm_consolefd, TIOCGWINSZ, &win) == -1) {
return;
} /*if*/
if (!win.ws_col || !win.ws_row) {
win.ws_col=80; win.ws_row=25;
} /*if*/
gpm_mx = win.ws_col - gpm_zerobased;
gpm_my = win.ws_row - gpm_zerobased;
}
#endif /* SIGWINCH */
#if (defined(SIGTSTP))
/* itz: support for SIGTSTP */
/* Old SIGTSTP handler. */
static struct sigaction gpm_saved_suspend_hook;
/*----------------------------------------------------------------------------*
* nice description
*----------------------------------------------------------------------------*/
static void gpm_suspend_hook (int signum)
{
Gpm_Connect gpm_connect;
sigset_t old_sigset;
sigset_t new_sigset;
struct sigaction sa;
int success;
sigemptyset (&new_sigset);
sigaddset (&new_sigset, SIGTSTP);
sigprocmask (SIG_BLOCK, &new_sigset, &old_sigset);
/* Open a completely transparent gpm connection */
gpm_connect.eventMask = 0;
gpm_connect.defaultMask = ~0;
gpm_connect.minMod = ~0;
gpm_connect.maxMod = 0;
/* cannot do this under xterm, tough */
success = (Gpm_Open (&gpm_connect, 0) >= 0);
/* take the default action, whatever it is (probably a stop :) */
sigprocmask (SIG_SETMASK, &old_sigset, 0);
sigaction (SIGTSTP, &gpm_saved_suspend_hook, 0);
kill (getpid (), SIGTSTP);
/* in bardo here */
/* Reincarnation. Prepare for another death early. */
sigemptyset(&sa.sa_mask);
sa.sa_handler = gpm_suspend_hook;
sa.sa_flags = SA_NOMASK;
sigaction (SIGTSTP, &sa, 0);
/* Pop the gpm stack by closing the useless connection */
/* but do it only when we know we opened one.. */
if (success) {
Gpm_Close ();
} /*if*/
}
#endif /* SIGTSTP */
/*----------------------------------------------------------------------------*
* nice description
*----------------------------------------------------------------------------*/
int Gpm_Open(Gpm_Connect *conn, int flag)
{
char *tty = NULL;
char *term = NULL;
int i;
struct sockaddr_un addr;
struct winsize win;
Gpm_Stst *new = NULL;
char* sock_name = 0;
static char *consolename = NULL;
int gpm_is_disabled = 0;
/*....................................... First of all, check xterm */
if ((term=(char *)getenv("TERM")) && !strncmp(term,"xterm",5)) {
if(gpm_tried) return gpm_fd; /* no stack */
gpm_fd=-2;
GPM_XTERM_ON;
gpm_flag=1;
return gpm_fd;
}
/*....................................... No xterm, go on */
/* check whether we know what name the console is: what's with the lib??? */
if(!consolename) {
consolename = Gpm_get_console();
if(!consolename) {
gpm_report(GPM_PR_ERR,"unable to open gpm console, check your /dev filesystem!\n");
goto err;
}
}
/*
* So I chose to use the current tty, instead of /dev/console, which
* has permission problems. (I am fool, and my console is
* readable/writeable by everybody.
*
* However, making this piece of code work has been a real hassle.
*/
if (!gpm_flag && gpm_tried) return -1;
gpm_tried=1; /* do or die */
if ((new = (Gpm_Stst *) malloc(sizeof(Gpm_Stst))) == NULL)
return -1;
new->next=gpm_stack;
gpm_stack=new;
conn->pid=getpid(); /* fill obvious values */
if (new->next)
conn->vc=new->next->info.vc; /* inherit */
else {
conn->vc=0; /* default handler */
if (flag > 0) { /* forced vc number */
conn->vc=flag;
if((tty = malloc(strlen(consolename) + Gpm_cnt_digits(flag))) == NULL)
gpm_report(GPM_PR_OOPS,GPM_MESS_NO_MEM);
memcpy(tty, consolename, strlen(consolename)-1);
sprintf(&tty[strlen(consolename) - 1], "%i", flag);
} else if (flag==0) { /* use your current vc */
if (isatty(0)) tty = ttyname(0); /* stdin */
if (!tty && isatty(1)) tty = ttyname(1); /* stdout */
if (!tty && isatty(2)) tty = ttyname(2); /* stderr */
if (tty == NULL) {
gpm_report(GPM_PR_ERR,"checking tty name failed");
goto err;
}
conn->vc=atoi(&tty[strlen(consolename)-1]);
} else /* a default handler -- use console */
tty=strdup(consolename);
if (gpm_consolefd == -1)
if ((gpm_consolefd=open(tty,O_WRONLY)) < 0) {
gpm_report(GPM_PR_ERR,GPM_MESS_DOUBLE_S,tty,strerror(errno));
goto err;
}
}
new->info=*conn;
/*....................................... Get screen dimensions */
ioctl(gpm_consolefd, TIOCGWINSZ, &win);
if (!win.ws_col || !win.ws_row) {
fprintf(stderr, "libgpm: zero screen dimension, assuming 80x25.\n");
win.ws_col=80; win.ws_row=25;
}
gpm_mx = win.ws_col - gpm_zerobased;
gpm_my = win.ws_row - gpm_zerobased;
/*....................................... Connect to the control socket */
if (!(gpm_flag++)) {
if ( (gpm_fd=socket(AF_UNIX,SOCK_STREAM,0))<0 ) {
gpm_report(GPM_PR_ERR,GPM_MESS_SOCKET,strerror(errno));
goto err;
}
#ifndef SO_PEERCRED
bzero((char *)&addr,sizeof(addr));
addr.sun_family=AF_UNIX;
if (!(sock_name = tempnam (0, "gpm"))) {
gpm_report(GPM_PR_ERR,GPM_MESS_TEMPNAM,strerror(errno));
goto err;
}
strncpy (addr.sun_path, sock_name, sizeof (addr.sun_path));
if (bind (gpm_fd, (struct sockaddr*)&addr,
sizeof (addr.sun_family) + strlen (addr.sun_path))==-1) {
gpm_report(GPM_PR_ERR,GPM_MESS_DOUBLE_S, sock_name, strerror(errno));
goto err;
}
#endif
bzero((char *)&addr,sizeof(addr));
addr.sun_family=AF_UNIX;
strcpy(addr.sun_path, GPM_NODE_CTL);
i=sizeof(addr.sun_family)+strlen(GPM_NODE_CTL);
if(connect(gpm_fd,(struct sockaddr *)(&addr),i)<0 ) {
struct stat stbuf;
if (errno == ENOENT) {
gpm_report(GPM_PR_DEBUG,"cannot find %s; ignoring (gpm disabled?)",
GPM_NODE_CTL);
gpm_is_disabled++;
} else {
gpm_report(GPM_PR_INFO,GPM_MESS_DOUBLE_S,GPM_NODE_CTL,
strerror(errno));
}
/*
* Well, try to open a chr device called /dev/gpmctl. This should
* be forward-compatible with a kernel server
*/
close(gpm_fd); /* the socket */
if ((gpm_fd=open(GPM_NODE_DEV,O_RDWR))==-1) {
if (errno == ENOENT) {
gpm_report(GPM_PR_DEBUG,"Cannot find %s; ignoring (gpm disabled?)",
GPM_NODE_DEV);
gpm_is_disabled++;
} else {
gpm_report(GPM_PR_ERR,GPM_MESS_DOUBLE_S,GPM_NODE_DEV
,strerror(errno));
}
goto err;
}
if (fstat(gpm_fd,&stbuf)==-1 || (stbuf.st_mode&S_IFMT)!=S_IFCHR) {
goto err;
}
}
}
/*....................................... Put your data */
if (putdata(gpm_fd,conn)!=-1) {
/* itz Wed Dec 16 23:22:16 PST 1998 use sigaction, the old
code caused a signal loop under XEmacs */
struct sigaction sa;
sigemptyset(&sa.sa_mask);
#if (defined(SIGWINCH))
/* And the winch hook .. */
sa.sa_handler = gpm_winch_hook;
sa.sa_flags = 0;
sigaction(SIGWINCH, &sa, &gpm_saved_winch_hook);
#endif
#if (defined(SIGTSTP))
if (gpm_flag == 1) {
/* Install suspend hook */
sa.sa_handler = SIG_IGN;
sigaction(SIGTSTP, &sa, &gpm_saved_suspend_hook);
/* if signal was originally ignored, job control is not supported */
if (gpm_saved_suspend_hook.sa_handler != SIG_IGN) {
sa.sa_flags = SA_NOMASK;
sa.sa_handler = gpm_suspend_hook;
sigaction(SIGTSTP, &sa, 0);
}
}
#endif
}
return gpm_fd;
/*....................................... Error: free all memory */
err:
if (gpm_is_disabled < 2) /* be quiet if no gpmctl socket found */
gpm_report(GPM_PR_ERR,"Oh, oh, it's an error! possibly I die! ");
while(gpm_stack) {
new=gpm_stack->next;
free(gpm_stack);
gpm_stack=new;
}
if (gpm_fd>=0) close(gpm_fd);
if (sock_name) {
unlink(sock_name);
free(sock_name);
sock_name = 0;
}
gpm_flag=0;
return -1;
}
/*-------------------------------------------------------------------*/
int Gpm_Close(void)
{
Gpm_Stst *next;
gpm_tried=0; /* reset the error flag for next time */
if (gpm_fd==-2) /* xterm */
GPM_XTERM_OFF;
else /* linux */
{
if (!gpm_flag) return 0;
next=gpm_stack->next;
free(gpm_stack);
gpm_stack=next;
if (next)
putdata(gpm_fd,&(next->info));
if (--gpm_flag) return -1;
}
if (gpm_fd>=0) close(gpm_fd);
gpm_fd=-1;
#ifdef SIGTSTP
sigaction(SIGTSTP, &gpm_saved_suspend_hook, 0);
#endif
#ifdef SIGWINCH
sigaction(SIGWINCH, &gpm_saved_winch_hook, 0);
#endif
close(gpm_consolefd);
gpm_consolefd=-1;
return 0;
}
/*-------------------------------------------------------------------*/
int Gpm_GetEvent(Gpm_Event *event)
{
int count;
MAGIC_P((int magic));
if (!gpm_flag) return 0;
#ifdef GPM_USE_MAGIC
if ((count=read(gpm_fd,&magic,sizeof(int)))!=sizeof(int))
{
if (count==0)
{
gpm_report(GPM_PR_INFO,"Warning: closing connection");
Gpm_Close();
return 0;
}
gpm_report(GPM_PR_INFO,"Read too few bytes (%i) at %s:%d",count,__FILE__,__LINE__);
return -1;
}
#endif
if ((count=read(gpm_fd,event,sizeof(Gpm_Event)))!=sizeof(Gpm_Event))
{
#ifndef GPM_USE_MAGIC
if (count==0)
{
gpm_report(GPM_PR_INFO,"Warning: closing connection");
Gpm_Close();
return 0;
}
#endif
/*
* avoid to send the message if there is no data; sometimes it makes
* sense to poll the mouse descriptor any now an then using a
* non-blocking descriptor
*/
if (count != -1 || errno != EAGAIN)
gpm_report(GPM_PR_INFO,"Read too few bytes (%i) at %s:%d",
count,__FILE__,__LINE__);
return -1;
}
event->x -= gpm_zerobased;
event->y -= gpm_zerobased;
return 1;
}
#define MAXNBPREVCHAR 4 /* I don't think more is usefull, JD */
static int nbprevchar=0, prevchar[MAXNBPREVCHAR];
/*-------------------------------------------------------------------*/
int Gpm_CharsQueued ()
{
return nbprevchar;
}
/*-------------------------------------------------------------------*/
int Gpm_Getc(FILE *f)
{
fd_set selSet;
int max, flag, result;
static Gpm_Event ev;
int fd=fileno(f);
static int count;
/* Hmm... I must be sure it is unbuffered */
if (!(count++))
setvbuf(f,NULL,_IONBF,0);
if (!gpm_flag) return getc(f);
/* If the handler asked to provide more keys, give them back */
if (gpm_morekeys) return (*gpm_handler)(&ev,gpm_data);
gpm_hflag=0;
max = (gpm_fd>fd) ? gpm_fd : fd;
/*...................................................................*/
if (gpm_fd>=0) /* linux */
while(1)
{
if (gpm_visiblepointer) GPM_DRAWPOINTER(&ev);
do
{
FD_ZERO(&selSet);
FD_SET(fd,&selSet);
if (gpm_fd>-1)
FD_SET(gpm_fd,&selSet);
gpm_timeout.tv_sec=SELECT_TIME;
flag=select(max+1,&selSet,(fd_set *)NULL,(fd_set *)NULL,&gpm_timeout);
}
while (!flag);
if (flag==-1)
continue;
if (FD_ISSET(fd,&selSet))
return fgetc(f);
if (Gpm_GetEvent(&ev) && gpm_handler
&& (result=(*gpm_handler)(&ev,gpm_data)))
{
gpm_hflag=1;
return result;
}
}
else
/*...................................................................*/
if (gpm_fd==-2) /* xterm */
{
#define DELAY_MS 100
static struct timeval to={0,DELAY_MS*1000};
static fd_set selSet;
extern int gpm_convert_event(unsigned char *data, Gpm_Event *event);
int c; unsigned char mdata[4];
if (nbprevchar) /* if there are some consumed char ... */
return prevchar[--nbprevchar];
while(1)
{
do
{
FD_ZERO(&selSet); FD_SET(fd,&selSet);
gpm_timeout.tv_sec=SELECT_TIME;
flag=select(fd+1,&selSet,(fd_set *)NULL,(fd_set *)NULL,&gpm_timeout);
}
while (!flag);
if ((c=fgetc(f))!=0x1b) return c;
/* escape: go on */
FD_ZERO(&selSet); FD_SET(fd,&selSet); to.tv_usec=DELAY_MS*1000;
if ((flag=select(fd+1,&selSet,(fd_set *)NULL,(fd_set *)NULL,&to))==0)
return c;
if ((c=fgetc(f))!='[')
{prevchar[nbprevchar++]=c; return 0x1B;} /* patche par JD 11/08/1998 */
/* '[': go on */
FD_ZERO(&selSet); FD_SET(fd,&selSet); to.tv_usec=DELAY_MS*1000;
if ((flag=select(fd+1,&selSet,(fd_set *)NULL,(fd_set *)NULL,&to))==0)
{prevchar[nbprevchar++]=c; return 0x1B;} /* patche par JD 11/08/1998 */
if ((c=fgetc(f))!='M')
/* patche par JD 11/08/1998 NOTICE: prevchar is a lifo !*/
{prevchar[nbprevchar++]=c; prevchar[nbprevchar++]='['; return 0x1B;}
/* now, it surely is a mouse event */
for (c=0;c<3;c++) mdata[c]=fgetc(f);
gpm_convert_event(mdata,&ev);
if (gpm_handler && (result=(*gpm_handler)(&ev,gpm_data)))
{
gpm_hflag=1;
return result;
}
} /* while(1) */
}
/*...................................................................*/
else return fgetc(f); /* no mouse available */
}
/*-------------------------------------------------------------------*/
int Gpm_Repeat(int msec)
{
struct timeval to={0,1000*msec};
int fd;
fd_set selSet;
fd=gpm_fd>=0 ? gpm_fd : 0; /* either the socket or stdin */
FD_ZERO(&selSet);
FD_SET(fd,&selSet);
return (select(fd+1,&selSet,(fd_set *)NULL,(fd_set *)NULL,&to)==0);
}
/*-------------------------------------------------------------------*/
int Gpm_FitValuesM(int *x, int *y, int margin)
{
if (margin==-1)
{
*x = min( max(*x,!gpm_zerobased), gpm_mx);
*y = min( max(*y,!gpm_zerobased), gpm_my);
return 0;
}
switch(margin)
{
case GPM_TOP: (*y)++; break;
case GPM_BOT: (*y)--; break;
case GPM_RGT: (*x)--; break;
case GPM_LFT: (*x)++; break;
}
return 0;
}
/*-------------------------------------------------------------------*/
int gpm_convert_event(unsigned char *mdata, Gpm_Event *ePtr)
{
static struct timeval tv1={0,0}, tv2;
static int clicks=0;
int c;
#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)
/* Variable btn has following meaning: */
c = mdata[0]-32; /* 0="1-down", 1="2-down", 2="3-down", 3="up" */
if (c==3)
{
ePtr->type = GPM_UP | (GPM_SINGLE<<clicks);
/* ePtr->buttons = 0; */ /* no, keep info from press event */
GET_TIME (tv1);
clicks = 0;
}
else
{
ePtr->type = GPM_DOWN;
GET_TIME (tv2);
if (tv1.tv_sec && (DIF_TIME(tv1,tv2)<250)) /* 250ms for double click */
{clicks++; clicks%=3;}
else clicks = 0;
switch (c)
{
case 0: ePtr->buttons=GPM_B_LEFT; break;
case 1: ePtr->buttons=GPM_B_MIDDLE; break;
case 2: ePtr->buttons=GPM_B_RIGHT; break;
default: /* Nothing */ break;
}
}
/* Coordinates are 33-based */
/* Transform them to 1-based */
ePtr->x = mdata[1]-32-gpm_zerobased;
ePtr->y = mdata[2]-32-gpm_zerobased;
return 0;
}
/* Local Variables: */
/* c-indent-level: 3 */
/* End: */