/*
* gpm-root.y - a default-handler for mouse events (gpm-Linux)
*
* Copyright 1994,1995 rubini@linux.it (Alessandro Rubini)
* Copyright (C) 1998 Ian Zimmerman <itz@rahul.net>
*
* Tue, 5 Jan 1999 23:16:45 +0000, modified by James Troup <james@nocrew.org>:
* (get_winsize): use /dev/tty0 not /dev/console.
* gpm-root.y (f.debug): disable undocumented f.debug function because it uses a
* file in /tmp in a fashion which invites symlink abuse.
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syslog.h>
#include <signal.h> /* sigaction() */
#include <pwd.h> /* pwd entries */
#include <grp.h> /* initgroups() */
#include <sys/kd.h> /* KDGETMODE */
#include <sys/stat.h> /* fstat() */
#include <sys/utsname.h> /* uname() */
#include <termios.h> /* winsize */
#include <linux/vt.h> /* VT_ACTIVATE */
#include <linux/keyboard.h> /* K_SHIFT */
#include <utmp.h>
#include <endian.h>
#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#else
#define major(dev) (((unsigned) (dev))>>8)
#define minor(dev) ((dev)&0xff)
#endif
#define GPM_NULL_DEV "/dev/null"
#ifdef HAVE_LINUX_MAJOR_H
#include <linux/major.h>
#else
#define VCS_MAJOR 7
#endif
#define MAX_NR_USER_CONSOLES 63 /* <linux/tty.h> needs __KERNEL__ */
#include "headers/message.h"
#include "headers/gpm.h"
#ifdef DEBUG
#define YYDEBUG 1
#else
#undef YYDEBUG
#endif
#define USER_CFG ".gpm-root"
#define SYSTEM_CFG SYSCONFDIR "/gpm-root.conf"
#define DEFAULT_FORE 7
#define DEFAULT_BACK 0
#define DEFAULT_BORD 7
#define DEFAULT_HEAD 7
/* These macros are useful to avoid curses. The program is unportable anyway */
#define GOTOXY(f,x,y) fprintf(f,"\x1B[%03i;%03iH",y,x)
#define FORECOLOR(f,c) fprintf(f,"\x1B[%i;3%cm",(c)&8?1:22,colLut[(c)&7]+'0')
#define BACKCOLOR(f,c) fprintf(f,"\x1B[4%cm",colLut[(c)&7]+'0')
/* These defines are ugly hacks but work */
#define ULCORNER 0xc9
#define URCORNER 0xbb
#define LLCORNER 0xc8
#define LRCORNER 0xbc
#define HORLINE 0xcd
#define VERLINE 0xba
int colLut[]={0,4,2,6,1,5,3,7};
char *prgname;
char *consolename;
int run_status = GPM_RUN_STARTUP;
struct winsize win;
int disallocFlag=0;
struct node {char *name; int flag;};
struct node tableMod[]= {
{"shift", 1<<KG_SHIFT},
{"anyAlt", 1<<KG_ALT | 1<<KG_ALTGR},
{"leftAlt", 1<<KG_ALT},
{"rightAlt", 1<<KG_ALTGR},
{"control", 1<<KG_CTRL},
{NULL,0}
};
/* provide defaults */
int opt_mod = 4; /* control */
int opt_buf = 0; /* ask the kernel about it */
int opt_user = 1; /* allow user cfg files */
typedef struct DrawItem {
short type;
short pad;
char *name;
char *arg; /* a cmd string */
void *clientdata; /* a (Draw *) for menus or whatever */
int (*fun)();
struct DrawItem *next;
} DrawItem;
typedef struct Draw {
short width; /* length of longest item */
short height; /* the number of items */
short uid; /* owner */
short buttons; /* which button */
short fore,back,bord,head; /* colors */
char *title; /* name */
time_t mtime; /* timestamp of source file */
DrawItem *menu; /* the list of items */
struct Draw *next; /* chain */
} Draw;
typedef struct Posted {
short x,y,X,Y;
Draw *draw;
unsigned char *dump;
short colorcell;
struct Posted *prev;
} Posted;
Draw *drawList=NULL;
/* support functions and vars */
int yyerror(char *s);
int yylex(void);
DrawItem *cfg_cat(DrawItem *, DrawItem *);
DrawItem *cfg_makeitem(int mode, char *msg, int(*fun)(), void *detail);
/*===================================================================*
* This part of the source is devoted to reading the cfg file
*/
char cfgname[256];
FILE *cfgfile=NULL;
int cfglineno=0;
Draw *cfgcurrent, *cfgall;
Draw *cfg_alloc(void);
/* prototypes for predefined functions */
enum F_call {F_CREATE, F_POST, F_INVOKE, F_DONE};
int f_debug(int mode, DrawItem *self, int uid);
int f_bgcmd(int mode, DrawItem *self, int uid);
int f_fgcmd(int mode, DrawItem *self, int uid);
int f_jptty(int mode, DrawItem *self, int uid);
int f_mktty(int mode, DrawItem *self, int uid);
int f_menu(int mode, DrawItem *self, int uid);
int f_lock(int mode, DrawItem *self, int uid);
int f_load(int mode, DrawItem *self, int uid);
int f_free(int mode, DrawItem *self, int uid);
int f_time(int mode, DrawItem *self, int uid);
int f_pipe(int mode, DrawItem *self, int uid);
%} /* and this starts yacc definitions */
%union {
int silly;
char *string;
Draw *draw;
DrawItem *item;
int (*fun)();
}
%token <string> T_STRING
%token <silly> T_BACK T_FORE T_BORD T_HEAD
%token <silly> T_BRIGHT T_COLOR T_NAME
%token T_BUTTON
%token <fun> T_FUNC T_FUN2
%type <silly> bright button
%type <draw> menu file
%type <item> item items itemlist
%% /* begin grammar #########################################################*/
file: /* empty */ {$$=cfgall=NULL;}
| file button menu {$3->buttons=$2; $3->next=$1; $$=cfgall=$3;}
;
button: T_BUTTON '1' {$$=GPM_B_LEFT;}
| T_BUTTON '2' {$$=GPM_B_MIDDLE;}
| T_BUTTON '3' {$$=GPM_B_RIGHT;}
;
menu: '{' {$<draw>$=cfgcurrent=cfg_alloc();}
configs itemlist '}' {$$=$<draw>2; $$->menu=$4;}
;
configs: /* empty */ | configs cfgpair ;
cfgpair: T_NAME T_STRING {cfgcurrent->title=$2;}
| T_BACK T_COLOR {cfgcurrent->back=$2;}
| T_FORE bright T_COLOR {cfgcurrent->fore=$3|$2;}
| T_BORD bright T_COLOR {cfgcurrent->bord=$3|$2;}
| T_HEAD bright T_COLOR {cfgcurrent->head=$3|$2;}
;
bright: /* empty */ {$$=0;} | T_BRIGHT {$$=8;} ;
itemlist: item items {$$=cfg_cat($1,$2);} ;
items: /* empty */ {$$=NULL;}
| items item {$$= $1 ? cfg_cat($1,$2) : $2;}
;
item: T_STRING T_FUNC {$$=cfg_makeitem('F',$1,$2, NULL);}
| T_STRING T_FUN2 T_STRING {$$=cfg_makeitem('2',$1,$2, $3);}
| T_STRING menu {$$=cfg_makeitem('M',$1,NULL,$2);}
;
%% /* end grammar ###########################################################*/
int yyerror(char *s)
{
fprintf(stderr,"%s:%s(%i): %s\n",prgname,cfgname,cfglineno,s);
return 1;
}
int yywrap()
{
return 1;
}
struct tokenName {
char *name;
int token;
int value;
};
struct tokenName tokenList[] = {
{"foreground",T_FORE,0},
{"background",T_BACK,0},
{"border", T_BORD,0},
{"head", T_HEAD,0},
{"name", T_NAME,0},
{"button", T_BUTTON,0},
{"black", T_COLOR,0},
{"blue", T_COLOR,1},
{"green", T_COLOR,2},
{"cyan", T_COLOR,3},
{"red", T_COLOR,4},
{"magenta", T_COLOR,5},
{"yellow", T_COLOR,6},
{"white", T_COLOR,7},
{"bright", T_BRIGHT,0},
{NULL,0,0}
};
struct funcName {
char *name;
int token;
int (*fun)();
};
struct funcName funcList[] = {
{"f.debug",T_FUNC,f_debug},
{"f.fgcmd",T_FUN2,f_fgcmd},
{"f.bgcmd",T_FUN2,f_bgcmd},
{"f.jptty",T_FUN2,f_jptty},
{"f.mktty",T_FUNC,f_mktty},
{"f.menu",T_FUNC,f_menu},
{"f.lock",T_FUN2,f_lock}, /* "lock one", "lock all" */
{"f.load",T_FUNC,f_load},
{"f.free",T_FUNC,f_free},
{"f.time",T_FUNC,f_time},
{"f.pipe",T_FUN2,f_pipe},
{"f.nop",T_FUNC,NULL},
{NULL,0,NULL}
};
/*---------------------------------------------------------------------*/
int yylex(void)
{
int c,i;
char s[80];
struct tokenName *tn;
struct funcName *fn;
while(1) {
i=0;
switch(c=getc(cfgfile)) {
case EOF: fclose(cfgfile); return 0;
case '\"':
do {
s[i]=getc(cfgfile);
if ((s[i])=='\n') {
yyerror("unterminated string");
cfglineno++;
}
if (s[i]=='\\') s[i]=getc(cfgfile);
} /* get '"' as '\"' */ while (s[i++]!='\"' && s[i-2] !='\\') ;
s[i-1]=0;
yylval.string=(char *)strdup(s);
return T_STRING;
case '#': while ( (c=getc(cfgfile)!='\n') && c!=EOF) ;
case '\n': cfglineno++;
case ' ': /* fall through */
case '\t': continue;
default: if (!isalpha(c)) return(c);
}
/* get a single word and convert it */
do {
s[i++]=c;
} while (isalnum(c=getc(cfgfile)) || c=='.');
ungetc(c,cfgfile);
s[i]=0;
for (tn=tokenList; tn->name; tn++)
if (tn->name[0]==s[0] && !strcmp(tn->name,s)) {
yylval.silly=tn->value;
return tn->token;
}
for (fn=funcList; fn->name; fn++)
if (fn->name[0]==s[0] && !strcmp(fn->name,s)) {
yylval.fun=fn->fun;
return fn->token;
}
yylval.string=(char *)strdup(s); return T_STRING;
}
}
/*---------------------------------------------------------------------*/
void cfg_free(Draw *what)
{
Draw *ptr;
DrawItem *item;
for (ptr=what; ptr; ptr=ptr->next) {
if (ptr->title) free(ptr->title);
for (item=ptr->menu; item; item=item->next) {
if (item->name) free(item->name);
if (item->arg) free(item->arg);
if (item->type=='M' && item->clientdata) {
((Draw *)(item->clientdata))->next=NULL; /* redundant */
cfg_free(item->clientdata);
}
if (item->clientdata) free(item->clientdata);
}
}
}
/*---------------------------------------------------------------------*/
/* malloc an empty Draw */
Draw *cfg_alloc(void)
{
Draw *new=calloc(1,sizeof(Draw));
if (!new) return NULL;
new->back=DEFAULT_BACK;
new->fore=DEFAULT_FORE;
new->bord=DEFAULT_BORD;
new->head=DEFAULT_HEAD;
return new;
}
/*---------------------------------------------------------------------*/
/* malloc an empty DrawItem and fill it */
DrawItem *cfg_makeitem(int mode, char *msg, int(*fun)(), void *detail)
{
DrawItem *new=calloc(1,sizeof(DrawItem));
if (!new) return NULL;
new->name=(char *)strdup(msg);
new->type=mode;
switch(mode) {
case '2': /* a function with one arg */
new->arg=(char *)strdup(detail);
/* fall through */
case 'F': /* a function without args */
new->fun=fun;
if (fun) fun(F_CREATE,new);
break;
case 'M':
new->clientdata=detail;
new->fun=f_menu;
break;
default: fprintf(stderr,"%s: unknown item type (can't happen)\n",prgname);
}
return new;
}
/*---------------------------------------------------------------------*/
/* concatenate two item lists */
DrawItem *cfg_cat(DrawItem *d1, DrawItem *d2)
{
DrawItem *tmp;
for (tmp=d1; tmp->next; tmp=tmp->next) ;
tmp->next=d2;
return d1;
}
/*====================================================================*/
void f__fix(struct passwd *pass)
{
if (setgid(pass->pw_gid) < 0 ||
initgroups(pass->pw_name, pass->pw_gid) < 0 ||
setuid(pass->pw_uid) < 0)
exit(1);
setenv("HOME", pass->pw_dir, 1);
setenv("LOGNAME", pass->pw_name,1);
setenv("USER", pass->pw_name,1);
}
/*---------------------------------------------------------------------*/
static int f_debug_one(FILE *f, Draw *draw)
{
DrawItem *ip;
static int tc=0;
int i;
#define LINE(args) for(i=0;i<tc;i++) putc('\t',f); fprintf args
LINE((f,"BUTT %i - %ix%i\n",draw->buttons,draw->width,draw->height));
LINE((f,"UID %i\n",draw->uid));
LINE((f,"fore %i - back %i\n",draw->fore,draw->back));
LINE((f,"bord %i - head %i\n",draw->bord,draw->head));
LINE((f,"---> \"%s\" %li\n",draw->title,(long)(draw->mtime)));
for (ip=draw->menu; ip; ip=ip->next) {
LINE((f," %i \"%s\" (%p)\n",ip->type,ip->name,ip->fun));
if (ip->fun == f_menu) {
tc++; f_debug_one(f,(Draw *)ip->clientdata); tc--;
}
}
#undef LINE
return 0;
}
int f_debug(int mode, DrawItem *self, int uid)
{
#if 0 /* Disabled on account of security concerns; the way
* "/tmp/root-debug" is used is gratuitously
* open to symlink abuse */
FILE *f;
Draw *dp;
switch (mode) {
case F_POST:
if (!(f=fopen("/tmp/root-debug","a"))) return 1;
for(dp=drawList; dp; dp=dp->next)
f_debug_one(f,dp);
fprintf(f,"\n\n");
fclose(f);
case F_CREATE:
case F_INVOKE:
break;
}
#endif /* 0 */
return 0;
}
/*---------------------------------------------------------------------*/
int f_fgcmd(int mode, DrawItem *self, int uid)
{
switch (mode) {
case F_CREATE:
case F_POST: break;
case F_INVOKE: ; /* MISS */
}
return 0;
}
/*---------------------------------------------------------------------*/
int f_bgcmd(int mode, DrawItem *self, int uid)
{
int i;
struct passwd *pass;
switch (mode) {
case F_CREATE:
case F_POST: break;
case F_INVOKE:
switch(fork()) {
case -1:
gpm_report(GPM_PR_ERR, "fork(): %s", strerror(errno));
return 1;
case 0:
pass=getpwuid(uid);
if (!pass) exit(1);
f__fix(pass); /* setgid(), setuid(), setenv(), ... */
close(0); close(1); close(2);
open("/dev/null",O_RDONLY); /* stdin */
open(consolename,O_WRONLY); /* stdout */
dup(1); /* stderr */
int open_max = sysconf(_SC_OPEN_MAX);
if (open_max == -1) open_max = 1024;
for (i=3;i<open_max; i++) close(i);
execl("/bin/sh","sh","-c",self->arg,(char *)NULL);
exit(1); /* shouldn't happen */
default: return 0;
}
}
return 0;
}
/*---------------------------------------------------------------------*/
int f_jptty(int mode, DrawItem *self, int uid)
{
int i,fd;
switch (mode) {
case F_CREATE:
case F_POST: break;
case F_INVOKE:
i=atoi(self->arg);
fd=open(consolename,O_RDWR);
if (fd<0) {
gpm_report(GPM_PR_ERR, "%s: %s",consolename, strerror(errno));
return 1;
} /*if*/
if (ioctl(fd, VT_ACTIVATE, i)<0) {
gpm_report(GPM_PR_ERR, "%s: %s", consolename,strerror(errno));
return 1;
} /*if*/
if (ioctl(fd, VT_WAITACTIVE, i)<0) {
gpm_report(GPM_PR_ERR, "%s: %s", consolename,strerror(errno));
return 1;
}
default: return 0;
}
return 0; /* silly gcc -Wall */
}
/*---------------------------------------------------------------------*/
/* This array registers spawned consoles */
static int consolepids[1+MAX_NR_USER_CONSOLES];
int f_mktty(int mode, DrawItem *self, int uid)
{
int fd, pid;
int vc;
char name[10];
switch (mode) {
case F_CREATE: self->arg=malloc(8);
case F_POST: break;
case F_INVOKE:
fd=open(consolename,O_RDWR);
if (fd<0) {
gpm_report(GPM_PR_ERR,"%s: %s",consolename, strerror(errno));
return 1;
} /*if*/
if (ioctl(fd, VT_OPENQRY, &vc)<0) {
gpm_report(GPM_PR_ERR, "%s: %s",consolename, strerror(errno));
return 1;
} /*if*/
switch(pid=fork()) {
case -1:
gpm_report(GPM_PR_ERR, "fork(): %s", strerror(errno));
return 1;
case 0: /* child: exec getty */
sprintf(name,"tty%i",vc);
execl("/sbin/mingetty","mingetty",name,(char *)NULL);
exit(1); /* shouldn't happen */
default: /* father: jump to the tty */
gpm_report(GPM_PR_INFO,"Registering child %i on console %i"
,pid,vc);
consolepids[vc]=pid;
sprintf(self->arg,"%i",vc);
return f_jptty(mode,self,uid);
}
default: return 0;
}
return 0;
}
/*---------------------------------------------------------------------*/
int f_menu(int mode, DrawItem *self, int uid)
{
return 0; /* just a placeholder, recursion is performed in main() */
}
/*---------------------------------------------------------------------*/
int f_lock(int mode, DrawItem *self, int uid)
{
#if 0 /* some kind of interesting ...: if never */
int all;
static DrawItem msg = {
0,
10,
"Enter your password to unlock",
NULL, NULL, NULL, NULL
};
static Draw
switch (mode) {
case F_CREATE: /* either "one" or anything else */
if (strcmp(self->arg,"one")) self->arg[0]='a';
case F_POST: break;
case F_INVOKE: /* the biggest of all... */
}
#endif
return 0;
}
/*---------------------------------------------------------------------*/
int f_load(int mode, DrawItem *self, int uid)
{
FILE *f;
double l1,l2,l3;
l1=l2=l3=0.0;
switch (mode) {
case F_CREATE: /* modify name, just to fake its length */
self->clientdata=malloc(strlen(self->name)+20);
self->name=realloc(self->name,strlen(self->name)+20);
strcpy(self->clientdata,self->name);
strcat(self->clientdata," %5.2f %5.2f %5.2f");
sprintf(self->name,self->clientdata,l1,l2,l3);
break;
case F_POST:
if (!(f=fopen("/proc/loadavg","r"))) return 1;
fscanf(f,"%lf %lf %lf",&l1,&l2,&l3);
sprintf(self->name,self->clientdata,l1,l2,l3);
fclose(f);
case F_INVOKE: break;
}
return 0;
}
/*---------------------------------------------------------------------*/
int f_free(int mode, DrawItem *self, int uid)
{
FILE *f;
long l1,l2;
char s[80];
l1=l2=0;
switch (mode) {
case F_CREATE: /* modify name, just to fake its length */
self->clientdata=malloc(strlen(self->name)+30);
self->name=realloc(self->name,strlen(self->name)+30);
strcpy(self->clientdata,self->name);
strcat(self->clientdata," %5.2fM mem + %5.2fM swap");
sprintf(self->name,self->clientdata,(double)l1,(double)l2);
break;
case F_POST:
if (!(f=fopen("/proc/meminfo","r"))) return 1;
fgets(s,80,f);
fgets(s,80,f); sscanf(s,"%*s %*s %*s %li",&l1);
fgets(s,80,f); sscanf(s,"%*s %*s %*s %li",&l2);
sprintf(self->name,self->clientdata,
(double)l1/1024/1024,(double)l2/1024/1024);
fclose(f);
case F_INVOKE: break;
}
return 0;
}
/*---------------------------------------------------------------------*/
int f_time(int mode, DrawItem *self, int uid) {
char s[128];
struct tm *broken;
time_t t;
time(&t); broken=localtime(&t);
switch (mode) {
case F_CREATE: /* modify name, just to fake its length */
self->clientdata=self->name;
strftime(s,110,self->clientdata,broken);
strcat(s,"1234567890"); /* names can change length */
self->name=(char *)strdup(s);
/* rewrite the right string */
strftime(self->name,110,self->clientdata,broken);
break;
case F_POST: strftime(self->name,120,self->clientdata,broken);
case F_INVOKE: break;
}
return 0;
}
/*---------------------------------------------------------------------*/
int f_pipe(int mode, DrawItem *self, int uid)
{
return 0;
}
/*====================================================================*/
int fixone(Draw *ptr, int uid)
{
int hei,wid;
DrawItem *item;
ptr->uid=uid;
hei=0; wid= ptr->title? strlen(ptr->title)+2 : 0;
/* calculate width and height */
for (item=ptr->menu; item; item=item->next) {
hei++;
wid= wid > strlen(item->name) ? wid : strlen(item->name);
}
ptr->height=hei+2;
ptr->width=wid+2;
/* fix paddings and recurse */
for (item=ptr->menu; item; item=item->next) {
item->pad=(ptr->width-strlen(item->name ? item->name : ""))/2;
if (item->fun==f_menu) fixone((Draw *)item->clientdata,uid);
}
return 0;
}
/* read menus from a file, and return a list or NULL */
Draw *cfg_read(int uid)
{
Draw *ptr;
if (!(cfgfile=fopen(cfgname,"r"))) {
gpm_report(GPM_PR_ERR, "%s: %s", cfgname, strerror(errno));
return NULL;
}
gpm_report(GPM_PR_INFO,"Reading file %s",cfgname);
cfglineno=1;
if (yyparse()) {
cfg_free(cfgall);
cfgall=NULL;
return NULL;
}
/* handle recursion */
for (ptr=cfgall; ptr; ptr=ptr->next) {
fixone(ptr,uid);
}
return cfgall;
}
/*---------------------------------------------------------------------*/
/* the return value tells whether it has been newly loaded or not */
int getdraw(int uid, int buttons, time_t mtime1, time_t mtime2)
{
struct passwd *pass;
struct stat buf;
Draw *new, *np, *op, *pp;
int retval=0;
time_t mtime;
gpm_report(GPM_PR_DEBUG,"getdraw: %i %i %li %li",uid,buttons,mtime1,mtime2);
pass=getpwuid(uid);
/* deny personal cfg to root for security reasons */
if (pass==NULL || !uid || !opt_user) {
mtime=mtime2; uid=-1;
strcpy(cfgname,SYSTEM_CFG);
} else {
mtime=mtime1;
strcpy(cfgname,pass->pw_dir);
strcat(cfgname,"/" USER_CFG);
}
if (stat(cfgname,&buf)==-1) {
gpm_report(GPM_PR_DEBUG,"stat (%s) failed",cfgname);
/* try the system wide */
mtime=mtime2; uid = -1;
strcpy(cfgname,SYSTEM_CFG);
if (stat(cfgname,&buf)==-1) {
gpm_report(GPM_PR_ERR,"stat (%s) failed",cfgname);
return 0;
}
}
if (buf.st_mtime <= mtime) return 0;
/* else, read the new drawing tree */
new=cfg_read(uid);
if (!new) return 0;
/* scan old data to remove duplicates */
for (np=pp=new; np; pp=np, np=np->next) {
np->mtime=buf.st_mtime;
if (np->buttons==buttons) retval++;
for (op=drawList; op; op=op->next)
if (op->uid==np->uid && op->buttons==np->buttons)
op->buttons=0; /* mark for deletion */
}
/* chain in */
pp->next=drawList; drawList=new;
/* actually remove fake entries */
for (np=drawList; np; pp=np, np=np->next)
if (!np->buttons) {
pp->next=np->next;
np->next=NULL;
cfg_free(np);
np=pp;
}
return retval; /* found or not */
}
/*---------------------------------------------------------------------*/
Draw *retrievedraw(int uid, int buttons)
{
Draw *drawPtr, *genericPtr=NULL;
/* retrieve a drawing by scanning the list */
do {
for (drawPtr=drawList; drawPtr; drawPtr=drawPtr->next) {
if (drawPtr->uid==uid && drawPtr->buttons==buttons) break;
if (drawPtr->uid==-1 && drawPtr->buttons==buttons) genericPtr=drawPtr;
}
} while (getdraw(uid,buttons,
drawPtr ? drawPtr->mtime : 0,
genericPtr ? genericPtr->mtime :0));
return drawPtr ? drawPtr : genericPtr;
}
/*=====================================================================*/
int usage(void)
{
printf( GPM_MESS_VERSION "\n"
"Usage: %s [options]\n",prgname);
printf(" Valid options are\n"
" -m <number-or-name> modifier to use\n"
" -u inhibit user configuration files\n"
" -D don't auto-background and run as daemon\n"
" -V <verbosity-delta> increase amount of logged messages\n"
);
return 1;
}
/*------------*/
int getmask(char *arg, struct node *table)
{
int last=0, value=0;
char *cur;
struct node *n;
if (isdigit(arg[0])) return atoi(arg);
while (1) {
while (*arg && !isalnum(*arg)) arg++; /* skip delimiters */
cur=arg;
while(isalnum(*cur)) cur++; /* scan the word */
if (!*cur) last++;
*cur=0;
for (n=table;n->name;n++)
if (!strcmp(n->name,arg)) {
value |= n->flag;
break;
}
if(!n->name) fprintf(stderr,"%s: Incorrect flag \"%s\"\n",prgname,arg);
if (last) break;
cur++; arg=cur;
}
return value;
}
/*------------*/
int cmdline(int argc, char **argv)
{
int opt;
run_status = GPM_RUN_STARTUP;
while ((opt = getopt(argc, argv,"m:uDV::")) != -1) {
switch (opt) {
case 'm': opt_mod=getmask(optarg, tableMod); break;
case 'u': opt_user=0; break;
case 'D': run_status = GPM_RUN_DEBUG; break;
case 'V':
/*gpm_debug_level += (0==optarg ? 1 : strtol(optarg,0,0)); */
break;
default: return 1;
}
}
return 0;
}
/*------------*
* This buffer is passed to set_selection, and the only meaningful value
* is the last one, which is the mode: 4 means "clear_selection".
* however, the byte just before the 1th short must be 2 which denotes
* the selection-related stuff in ioctl(TIOCLINUX).
*/
static unsigned short clear_sel_args[6]={0, 0,0, 0,0, 4};
static unsigned char *clear_sel_arg= (unsigned char *)clear_sel_args+1;
/*------------*/
static inline void scr_dump(int fd, FILE *f, unsigned char *buffer, int vc)
{
int dumpfd;
char dumpname[20];
sprintf(dumpname,"/dev/vcsa%i",vc);
dumpfd=open(dumpname,O_RDONLY);
if (dumpfd<0) {
gpm_report(GPM_PR_ERR,"%s: %s", dumpname, strerror(errno));
return;
} /*if*/
clear_sel_arg[0]=2; /* clear_selection */
ioctl(fd,TIOCLINUX,clear_sel_arg);
read(dumpfd,buffer,4);
read(dumpfd,buffer+4,2*buffer[0]*buffer[1]);
close(dumpfd);
}
/*------------*/
static inline void scr_restore(int fd, FILE *f, unsigned char *buffer, int vc)
{
int x,y, dumpfd;
char dumpname[20];
x=buffer[2]; y=buffer[3];
/* WILL NOT WORK WITH DEVFS! FIXME! */
sprintf(dumpname,"/dev/vcsa%i",vc);
dumpfd=open(dumpname,O_WRONLY);
if (dumpfd<0) {
gpm_report(GPM_PR_ERR,"%s: %s", dumpname, strerror(errno));
return;
} /*if*/
clear_sel_arg[0]=2; /* clear_selection */
ioctl(fd,TIOCLINUX,clear_sel_arg);
write(dumpfd,buffer,4+2*buffer[0]*buffer[1]);
close(dumpfd);
}
/*===================================================================*/
/* post and unpost menus from the screen */
static int postcount;
static Posted *activemenu;
#if __BYTE_ORDER == __BIG_ENDIAN
#define bigendian 1
#else
#define bigendian 0
#endif
Posted *postmenu(int fd, FILE *f, Draw *draw, int x, int y, int console)
{
Posted *new;
DrawItem *item;
unsigned char *dump;
unsigned char *curr, *curr2;
int i;
short lines,columns;
new=calloc(1,sizeof(Posted));
if (!new) return NULL;
new->draw=draw;
new->dump=dump=malloc(opt_buf);
scr_dump(fd,f,dump,console);
lines=dump[0]; columns=dump[1];
i=(columns*dump[3]+dump[2])*2+1; /* where to get it */
if (i<0) i=1;
new->colorcell=dump[4+i-bigendian];
gpm_report(GPM_PR_DEBUG,"Colorcell=%02x (at %i,%i = %i)",
new->colorcell,dump[2],dump[3],i-bigendian);
/* place the box relative to the mouse */
if (!postcount) x -= draw->width/2; else x+=2;
y++;
/* fit inside the screen */
if (x<1) x=1;
if (x+draw->width >= columns) x=columns-1-draw->width;
if (y+draw->height > lines+1) y=lines+1-draw->height;
new->x=x; new->X=x+draw->width-1;
new->y=y; new->Y=y+draw->height-1;
/* these definitions are dirty hacks, but they help in writing to the screen */
#if __BYTE_ORDER == __BIG_ENDIAN
#define PUTC(c,f,b) (*(curr++)=((b)<<4)+(f),*(curr++)=(c))
#else
#define PUTC(c,f,b) (*(curr++)=(c),*(curr++)=((b)<<4)+(f))
#endif
#define PUTS(s,f,b) for(curr2=s;*curr2;PUTC(*(curr2++),f,b))
#define GOTO(x,y) (curr=dump+4+2*((y)*columns+(x)))
x--; y--; /* /dev/vcs is zero based */
ioctl(fd,TCXONC,TCOOFF); /* inhibit further prints */
dump=malloc(opt_buf);
memcpy(dump,new->dump,opt_buf); /* dup the buffer */
/* top border */
GOTO(x,y);
PUTC(ULCORNER,draw->bord,draw->back);
for (i=0; i<draw->width; i++) PUTC(HORLINE,draw->bord,draw->back);
PUTC(URCORNER,draw->bord,draw->back);
if (draw->title) {
GOTO(x+(draw->width-strlen(draw->title))/2,y);
PUTC(' ',draw->head,draw->back);
PUTS(draw->title,draw->head,draw->back);
PUTC(' ',draw->head,draw->back);
}
/* sides and items */
for (item=draw->menu; y++, item; item=item->next) {
if (item->fun) (*(item->fun))(F_POST,item);
GOTO(x,y); PUTC(VERLINE,draw->bord,draw->back);
for (i=0;i<item->pad;i++) PUTC(' ',draw->fore,draw->back);
PUTS(item->name,draw->fore,draw->back); i+=strlen(item->name);
while (i++<draw->width) PUTC(' ',draw->fore,draw->back);
PUTC(VERLINE,draw->bord,draw->back);
}
/* bottom border */
GOTO(x,y);
PUTC(LLCORNER,draw->bord,draw->back);
for (i=0; i<draw->width; i++) PUTC(HORLINE,draw->bord,draw->back);
PUTC(LRCORNER,draw->bord,draw->back);
scr_restore(fd,f,dump,console);
free(dump);
#undef PUTC
#undef PUTS
#undef GOTO
new->prev=activemenu;
activemenu=new;
postcount++;
return new;
}
Posted *unpostmenu(int fd, FILE *f, Posted *which, int vc)
{
Posted *prev=which->prev;
scr_restore(fd,f,which->dump, vc);
ioctl(fd,TCXONC,TCOON); /* activate the console */
free(which->dump);
free(which);
activemenu=prev;
postcount--;
return prev;
}
void reap_children(int signo)
{
int i, pid;
pid=wait(&i);
gpm_report(GPM_PR_INFO,"pid %i exited %i",pid,i);
if (disallocFlag)
gpm_report(GPM_PR_INFO,"Warning, overriding logout from %i",disallocFlag);
for (i=1;i<=MAX_NR_USER_CONSOLES; i++)
if (consolepids[i]==pid) {
disallocFlag=i;
consolepids[i]=0;
gpm_report(GPM_PR_INFO,"Registering disallocation of console %i",i);
break;
}
}
void get_winsize(void)
{
int fd;
if ((fd=open(consolename,O_RDONLY))<0) {
fprintf(stderr,"%s: ",prgname); perror(consolename);
exit(1);
}
ioctl(fd, TIOCGWINSZ, &win);
opt_buf=win.ws_col*win.ws_row;
close(fd);
opt_buf +=4; /* 2:size, 1:terminator, 1:alignment */
opt_buf*=2; /* the new scrdump and /dev/vcsa returns color info as well */
}
/*===================================================================*/
static int do_resize=0;
#if defined(__GLIBC__)
__sighandler_t winchHandler(int errno);
#else /* __GLIBC__ */
void winchHandler(int errno);
#endif /* __GLIBC__ */
int main(int argc, char **argv)
{
Gpm_Connect conn;
Gpm_Event ev;
int vc, fd=-1 ,uid=-1;
FILE *f=NULL;
struct stat stbuf;
Draw *draw=NULL;
DrawItem *item;
char s[80];
int posty = 0, postx, postX;
struct sigaction childaction;
int evflag;
int recursenow=0; /* not on first iteration */
prgname=argv[0];
consolename = Gpm_get_console();
setuid(0); /* if we're setuid, force it */
if (getuid()) {
fprintf(stderr,"%s: Must be root\n", prgname);
exit(1);
}
/*
* Now, first of all we need to check that /dev/vcs is there.
* But only if the kernel is new enough. vcs appeared in 1.1.82.
* If an actual open fails, a message on syslog will be issued.
*/
{
struct utsname linux_info;
int v1,v2,v3;
struct stat sbuf;
if (uname(&linux_info)) {
fprintf(stderr,"%s: uname(): %s\n",prgname,strerror(errno));
exit(1);
}
sscanf(linux_info.release,"%d.%d.%d",&v1,&v2,&v3);
if (v1*1000000 + v2*1000 +v3 < 1001082) {
fprintf(stderr,"%s: can't run with linux < 1.1.82\n",prgname);
exit(1);
}
/* problems with devfs! FIXME! */
if (stat("/dev/vcs0",&sbuf)<0 && stat("/dev/vcs",&sbuf)<0) {
fprintf(stderr,"%s: /dev/vcs0: %s\n",prgname,strerror(errno));
fprintf(stderr,"%s: do you have vcs devices? Refer to the manpage\n",
prgname);
exit(1);
} else if (!S_ISCHR(sbuf.st_mode) ||
VCS_MAJOR != major(sbuf.st_rdev) ||
0 != minor(sbuf.st_rdev)) {
fprintf(stderr,"Your /dev/vcs device looks funny\n");
fprintf(stderr,"Refer to the manpage and possibly run the"
"create_vcs script in gpm source directory\n");
exit(1);
}
}
if (cmdline(argc,argv)) exit(usage());
openlog(prgname, LOG_PID|LOG_CONS, run_status == GPM_RUN_DAEMON ?
LOG_DAEMON : LOG_USER);
/* reap your zombies */
childaction.sa_handler=reap_children;
sigemptyset(&childaction.sa_mask);
childaction.sa_flags=SA_INTERRUPT; /* need to break the select() call */
sigaction(SIGCHLD,&childaction,NULL);
/*....................................... Connect and get your buffer */
conn.eventMask=GPM_DOWN;
conn.defaultMask=GPM_MOVE; /* only ctrl-move gets the default */
conn.maxMod=conn.minMod=opt_mod;
gpm_zerobased=1;
for (vc=4; vc-->0;)
{
extern int gpm_tried; /* liblow.c */
gpm_tried=0; /* to enable retryings */
if (Gpm_Open(&conn,-1)!=-1)
break;
if (vc)
sleep(2);
}
if (!vc)
{
gpm_report(GPM_PR_OOPS,"can't open mouse connection");
}
conn.eventMask=~0; /* grab everything away form selection */
conn.defaultMask=GPM_MOVE & GPM_HARD;
conn.minMod=0;
conn.maxMod=~0;
chdir("/");
get_winsize();
/*....................................... Go to background */
if (run_status != GPM_RUN_DEBUG) {
switch(fork()) {
case -1: gpm_report(GPM_PR_OOPS,"fork()"); /* error */
case 0: run_status = GPM_RUN_DAEMON; break; /* child */
default: _exit(0); /* parent */
}
/* redirect stderr to /dev/console -- avoided now.
we should really cleans this more up! */
fclose(stdin); fclose(stdout);
if (!freopen(GPM_NULL_DEV,"w",stderr)) {
gpm_report(GPM_PR_OOPS,"freopen(stderr)");
}
if (setsid()<0)
gpm_report(GPM_PR_OOPS,"setsid()");
} /*if*/
/*....................................... Loop */
while((evflag=Gpm_GetEvent(&ev))!=0)
{
if (do_resize) {get_winsize(); do_resize--;}
if (disallocFlag)
{
struct utmp *uu;
struct utmp u;
char s[8];
int i=0;
gpm_report(GPM_PR_INFO,"Disallocating %i",disallocFlag);
ioctl(fileno(stdin),VT_DISALLOCATE,&i); /* all of them */
sprintf(s,"tty%i",disallocFlag);
setutent();
strncpy(u.ut_line, s, sizeof(u.ut_line));
if ((uu = getutline(&u)) != 0)
{
uu->ut_type = DEAD_PROCESS ;
pututline(uu);
}
disallocFlag=0;
}
if (evflag==-1) continue; /* no real event */
/* get rid of spurious events */
if (ev.type&GPM_MOVE) continue;
vc=ev.vc;
gpm_report(GPM_PR_DEBUG,"%s: event on console %i at %i, %i",
prgname,ev.vc,ev.x,ev.y);
if (!recursenow) /* don't open on recursion */
{
sprintf(s,"/dev/tty%i",ev.vc);
if (stat(s,&stbuf)==-1) continue;
uid = stbuf.st_uid;
gpm_report(GPM_PR_DEBUG,"uid = %i",uid);
draw=retrievedraw(uid,ev.buttons);
if (!draw) continue;
if (stat(s,&stbuf)==-1 || !(f=fopen(s,"r+"))) /* used to draw */
{
gpm_report(GPM_PR_ERR, "%s: %s", s, strerror(errno));
continue;
}
if ((fd=open(s,O_RDWR))<0) /* will O_RDONLY be enough? */
{
gpm_report(GPM_PR_ERR, "%s: %s", s, strerror(errno));
exit(1);
}
/* now change your connection information and manage the console */
Gpm_Open(&conn,-1);
uid=stbuf.st_uid;
}
/* the task now is drawing the box from user data */
if (!draw)
{
/* itz Thu Jul 2 00:02:53 PDT 1998 this cannot happen, see
continue statement above?!? */
gpm_report(GPM_PR_ERR,"NULL menu ptr while drawing");
continue;
}
postmenu(fd,f,draw,ev.x,ev.y,vc);
while(Gpm_GetEvent(&ev)>0 && ev.vc==vc)
{
Gpm_FitEvent(&ev);
if (ev.type&GPM_DOWN)
break; /* we're done */
Gpm_DrawPointer(ev.x,ev.y,fd);
}
gpm_report(GPM_PR_DEBUG,"%i - %i",posty,ev.y);
/* ok, redraw, close and return waiting */
gpm_report(GPM_PR_DEBUG,"Active is %p",activemenu->draw);
posty=activemenu->y;
postx=activemenu->x;
postX=activemenu->X;
recursenow=0; item=NULL; /* by default */
posty=ev.y-posty;
if (postx<=ev.x && ev.x<=postX) /* look for it */
{
for (item=draw->menu; posty-- && item; item=item->next)
gpm_report(GPM_PR_DEBUG,"item %s (%p)",item->name, item->fun);
if (item && item->fun && item->fun==f_menu)
{
recursenow++;
draw=item->clientdata;
continue;
}
}
/* unpost them all */
while (unpostmenu(fd,f,activemenu,vc))
;
close(fd);
fclose(f);
Gpm_Close();
recursenow=0; /* just in case... */
/* invoke the item */
if (item && item->fun)
(*(item->fun))(F_INVOKE,item,uid);
}
/*....................................... Done */
while (Gpm_Close()) ; /* close all the stack */
exit(0);
}
/* developers chat:
* author1 (possibly alessandro):
"This is because Linus uses 4-wide tabstops, forcing me to use the same
default to manage kernel sources"
* ian zimmermann (alias itz) on Wed Jul 1 23:28:13 PDT 1998:
"I don't mind what anybody's physical tab size is, but when I load it into
the editor I don't want any wrapping lines."
* nico schottelius (january 2002):
"Although Linux document /usr/src/linux/Documentation/CodingStyle is mostly
correct, I agree with itz to avoid wrapping lines. Merging 4(alessandro)
/2(itz) spaces makes 3 which is the current standard."
*/
/* Local Variables: */
/* tab-width:3 */
/* c-indent-level: 3 */
/* End: */