Plan 9 from Bell Labs’s /usr/web/sources/patch/sorry/cooked-mouse/cook.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <ctype.h>

/*
The cooked mode is implemented as an interpreter. In /sys/src/libdraw/cook.c:85 
is the program which implements the interpretation of events in a minilanguage. 
D and U mean up and down of a button and lower case 
letters are variables representing buttons. For example DiDjUjUi means pressing
one button, pressing another, releasing the second one pressed and then releasing the
first. T means the time passed is less than the quantum of time. 
M means the mouse is moved more than the quantum of movement. TTT> means
three quantum times or more.
Events generated can consume the state, or be preffixes (colouring one mouse struct or
all the generated in the event).

*/

#define MCHORDBITS(b,nth)	(((b)&0xf)<<(20 +(nth)*4))

enum {
	TIMERSTEP 	= 100,		/* in ms */
	SMALL 		= 1,		/* in pixels */
	MAXTOK		= 10,		/* for translation of events flag->TOKEN*/

	NOTMATCH=-1,
	IISPREFP=-2,
/* 
 * as explained in mouse(2), take care with MOUSEFLAGS
 */
	N =   0,
	L =   1,
	M =   2,
	R =   4,

	T_CANCEL 	= 2000
};

enum SMDATATYPE{
	TIMER,
	MOUSE
};

typedef struct Smdata Smdata;
struct Smdata {
	enum SMDATATYPE type;
	uint now;			/* miliseconds */
	Mouse;
};


int	verbstate;
char	state[1024];

extern Channel	*cookc;			/* chan(Mouse)[0] */
extern Channel	*modec;			/* chan(int)[0] */
extern int	mousemode;
static Smdata	smdata;

static Channel	*cntdownc = nil;	/* chan(int)[0], countdown start/end */

typedef struct Tatom Tatom;
struct Tatom {
	int mskold;
	int msknew;
	int negold;
	int negnew;
	char *tok;
};

static Tatom ttable[]={
	{L,L,0,1,"U1"},
	{M,M,0,1,"U2"},
	{R,R,0,1,"U3"},
	{L,L,1,0,"D1"},
	{M,M,1,0,"D2"},
	{R,R,1,0,"D3"},
};

typedef struct Pref Pref;
struct Pref {
	char *preffix;
	int isdone;	//has been done
	int ispref;  //when !preffix intepret this match as nop
	int isconsumer;	 //consume from the array
	int flags;
};

/*
	!ispreffix & !isconsumer == ispreffixonce == 00
	events,0,ispreffix,isconsumer,flags,   longest first.
	BUG: probably most are unnecessary.
	Order is important in consuming events.
*/

static Pref preffixes[]={
	{"DiT",0,0,0,MCLICK}, //click preffixonce
	{"DiUiTT>",0,0,1,MCLICK|MEND},	//click
	{"DiUiM",0,0,1,MCLICK|MEND},	//click
	{"DiUiTDiUi",0,0,1,MDOUBLE|MCLICK|MEND}, //double click
	{"DiDjDk",0,0,1,MDOUBLE|MCLICK|MCHORD|MEND}, //triple click-> double click
	{"DiM",0,1,0,MSELECT}, //slide preffix all
	{"DiMUi",0,0,1,MSELECT|MEND}, //slide
	{"DiDj",0,0,0,MCHORD},	//Chord preffix once
	{"DiDjM",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjUxUy",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjUi",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjUxM",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjMUx",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjMDx",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjDxM",0,0,1,MCLICK|MCHORD|MEND}, //Chord 
	{"DiDjUjDk",0,0,1,MDOUBLE|MCLICK|MCHORD|MEND}, //Chord Chord
	{"DiMDj",0,0,0,MSELECT|MCHORD}, //SLChord preffix once
	{"DiMDjUxUy",0,0,1,MSELECT|MCHORD|MEND}, //SLChord 
	{"DiMDjM",0,0,1,MSELECT|MCHORD|MEND}, //SLChord 
	{"DiMDjUjDk",0,0,1,MSELECT|MDOUBLE|MCLICK|MCHORD|MEND}, //Slide Chord Chord
//{"DiMDjUjDkM",0,0,1,MSELECT|MDOUBLE|MCLICK|MCHORD|MEND}, //Slide Chord Chord
};


static uint
msec(void)
{
	return nsec()/1000000;
}


static char *
transtable(Mouse mold, Mouse mnew)
{
	Tatom *t;
	char *data;
	int bold,bnew;
	int cndold,cndnew;
	int i;

	bold=mold.buttons;
	bnew=mnew.buttons;

	data=malloc(MAXTOK*nelem(ttable)+1);

	if(!data)
		sysfatal("transtable: Error mallocating data %r");
	else
		data[0]='\0';

	for(i=0;i<nelem(ttable);i++){
		t=&ttable[i];
		cndold=bold&t->mskold;
		cndnew=bnew & t->msknew;
		if(t->negold)
			cndold=!cndold;
		if(t->negnew)
			cndnew=!cndnew;
		if(cndold && cndnew)
			strcat(data,t->tok);
	}
	if(data[0])
		return data;
	else
		return nil;
}


static int
ismove(Mouse m1, Mouse m2)
{
	Rectangle near;
	int x0, y0, x1, y1;

	/* build a rectangle centered in ma->xy with sides aprox 2*SMALL */
	x0 = (m1.xy.x > SMALL) ? m1.xy.x-SMALL : m1.xy.x ;
	y0 = (m1.xy.y > SMALL) ? m1.xy.y-SMALL : m1.xy.y ;
	x1 = m1.xy.x + SMALL ;
	y1 = m1.xy.y + SMALL ;
	near = Rect(x0, y0, x1, y1);

	return(!ptinrect(m2.xy, near));
}


static int
commonpref(char *s1, char *s2)
{
	int len,i;
	
	len=strspn(s1,s2);
	for(i=0;i<len;i++){
		if(s1[i]!=s2[i])
			break;
	}
	return(i);
}

//returns flags codified on 
static int
isvar(char *pat, char *in, char *vars)
{
	int idx,val;

	//print("isvar(\"%s\",\"%s\")\n",pat,in);
	idx=(int)*pat-(int)'a';
	val=(int)*in-(int)'0';
	if(islower(*pat) && isdigit(*in)){
		if(!vars[idx]){
			vars[idx]=*in;
			return(val);
		}
		else if(vars[idx] && (vars[idx]==*in)){
			return(val);
		}
		else
			return(0);
	}
	else
		return(0);

}

static int
chordb(int flags, int b)
{
	if (!(flags&MCHORD0))
		return flags|MCHORDBITS(b, 0);
	if ((flags&MCHORD0) == MCHORDBITS(b, 0))
		return flags;
	if (!(flags&MCHORD1))
		return flags|MCHORDBITS(b, 1);
	if ((flags&MCHORD1) == MCHORDBITS(b, 1))
		return flags;
	if (!(flags&MCHORD2))
		return flags|MCHORDBITS(b, 2);
	if ((flags&MCHORD2) == MCHORDBITS(b, 2))
		return flags;
	return flags;
}

static long
imatch(char *pattern, char *input, int *flags)
{
	char vars[(int)'z'-(int)'a'+1]; //possible indexes
	char *pat, *in;
	int lmatch,lin,lpat;
	int res, ninrow,fl, isteing, justt;   //teing means matching in=T against pat=none


	ninrow=0;
	//if(verbose)
	//	print("imatch(\"%s\",\"%s\")\n",pattern,input);

	pat=pattern;
	in=input;
	lin=strlen(input);
	isteing=0;  
	justt=0;
	memset(vars,0,sizeof(vars));
	lmatch=0;


	for(;;){
		if(*pat!='M'){
			res=commonpref(pat,in);
			lmatch+=res;
			pat+=res;
			in+=res;
			lpat=strlen(pat);
			if(lmatch>=lin && justt && lpat>0){
				//		print("!length lmatch=%d lin=%d\n",lmatch,lin);
				return(NOTMATCH);
			}
			else if(lmatch>=lin && lpat>0 && (*pat!='>')){
				//		print("!length lmatch=%d lpat=%d\n",lmatch,lpat);
				return(IISPREFP);
			}
			else if(lmatch>=lin && !justt){
				//		print("!length lmatch=%d lin=%d\n",lmatch,lin);
				return(lmatch);
			}
			
		} 
		else
			res=0;

		if(!lmatch && (*in=='T')){
			justt=1;
		}


		if(res && (in[-1]=='T')){
			isteing=1;
		}

		//print("*pat:%c, res: %d\n",*pat,res);
		switch(*pat){
		case 'M':
			
			if((*in=='T')&& !isteing){
				in++;
				lmatch++;
				break;
			}else if (*in=='M'){
				in++;
				ninrow++;
				lmatch++;
				justt=0;
				break;
			}
			else if((*in!='M') && (ninrow!=0)){
				pat++;
				ninrow=0;
				justt=0;
			}
			else if(*in=='\0'){
				return(IISPREFP);
			}
			else
				return(NOTMATCH);
			isteing=0;
		break;
		case 'T':
			pat++;
			justt=0;
		break;
		case '>':
			if((lmatch>0) && (*in=='T') && (in[-1]=='T')&& (pat[-1]=='T')){
				in++;
				lmatch++;
				justt=0;
			}
			else if((lmatch>0) && (in[-1]=='T')&& (pat[-1]=='T') && !justt){
				pat++;
				justt=0;
			} 
			else if(justt)
				return(IISPREFP);
			else
				return(NOTMATCH);
			isteing=0; //not teing, matched an explicit rule
		break;
		default:	//variables
			if((*in=='T') && !isteing){
				in++;
				lmatch++;
			} 
			else if((lmatch>0)&&(*pat=='\0')){
				return(lmatch);
			}
			else if(fl=isvar(pat,in,vars)){
				*flags|=0x1<<(fl-1);
				*flags |= chordb(*flags, 0x1<<(fl-1)); //XXX
				in++;
				pat++;
				lmatch++;
				justt=0;
			}
			else{
				return(NOTMATCH);
			}
			isteing=0;
		break;
		}
	}
	return(NOTMATCH);
}


static char *
translate(Smdata *i,Mouse mold,int old)	
{
	char data[10],*res, *b;
	Mouse mnew;


	data[0]='\0';
	mnew = i->Mouse;
	if(i->type==TIMER){
		if ((i->now-old) < T_CANCEL) {
				strcat(data,"T");
		}
		
	}
	else{
		//print("!timer\n");
		if(ismove(mold,mnew)){
			strcat(data,"M");
		}
		if(b=transtable(mold,mnew)){
			strcat(data,b);
			free(b);
		}
	}

/*	if(i->type==TIMER){
		if ((i->now-old) > T_CANCEL)
			strcat(data,"T?");
	}
*/
	res=strdup(data);
	
	
	return(res);
}

static void
clean(void)
{
	int j;
	for(j=0;j<nelem(preffixes);j++)
		preffixes[j].isdone=0;
}

static void
consume(int len)
{
	memmove(state,state+len,strlen(state)+1);
}

/* 
 * if an event is detected, *m is actualized with the cooked values 
 * and "1" is returned. else *m is leaved intact and "0" is returned
 */
static int
cooker(Smdata *i)	
{
		
	static uint old;	/* msec() output */
	static Mouse mold;  /*start and end*/
	int yetnotmatch, matchall, matchpref, keepstate,output;
	Mouse m;	
	char *data;
	int j,lmatch,fl,slen;
	Pref *pref;

	fl=0;
	output=0;
	keepstate=0;
	m=i->Mouse;

	data=translate(i,mold,old);
	if(i->type!=TIMER){
		mold=m;
	}

	i->Mouse.buttons&=!MBUTTONS;
	if((i->type==TIMER) && (data[0]!='?')){
		old=i->now;
	}
	if(!data && (data[0]=='\0'))
		return(0);
	if((i->type!=TIMER) || (data[0]!='?'))
		nbsend(cntdownc, nil);	//activate timer if we get something

	strcat(state,data);
	free(data);
	slen=strlen(state);
	if(verbstate && (i->type!=TIMER))
		print("STATE: '%s'\n",state);
	if(state[0]=='\0')
		return(0);
	for(j=0;j<nelem(preffixes);j++){
		fl=0;
		pref=&preffixes[j];
		lmatch=imatch(pref->preffix,state,&fl);
		matchall=(lmatch==slen)&&(lmatch>0);
		yetnotmatch=(lmatch==IISPREFP);
		matchpref=(lmatch<slen)&&(lmatch>0);
		i->Mouse.buttons|=fl;
		fl=0;

		//preffix for all
		if((matchall||matchpref) && pref->ispref ){
			i->Mouse.buttons|=pref->flags;
			pref->isdone=1;
			if(verbstate)
				print("PREF %s %s\n",pref->preffix,state);
			output=1;
		}
		if(matchpref && !pref->ispref && !pref->isconsumer && !pref->isdone)
		{	
			if(verbstate)
				print("ONCE PREF %s %s\n",pref->preffix,state);
			pref->isdone=1;
			i->Mouse.buttons|=pref->flags;
			output=1;
		}
		if(matchall && pref->isconsumer){
			if(verbstate)
				print("MATCHALL %s\n",pref->preffix);
			i->Mouse.buttons|=pref->flags;
			if(pref->isconsumer){
				if(verbstate)
					print("CONSUME %s\n",state);
				consume(lmatch);
			}
			if(!pref->ispref)
				clean();
			
			output=1;
		}

		if(yetnotmatch){  //is on the way to matching
			keepstate=1;
			continue;
		}
		
	}
	if(!keepstate){
		if(verbstate && (strlen(state)>=2))
			print("NOMATCH: '%s'\n",state);
		consume(strlen(state));
		clean();
	}
	if(output){
		/* UGLY: clear MCHORD bits
		 * when event is not a chord.
		 */
		if (!(i->Mouse.buttons&MCHORD))
			i->Mouse.buttons &= ~MCHORDALL;
		return(1);
	} else
		return(0);
}


/*
 * not only cook but also implements mode changes
 */
static void
cookthread(void *arg)
{
	enum {
			ACDOWN,
			ACOOK,
			AMODE,
			NALT
	};

	Mousectl	*mc;
	Mouse 		m;
	int 		mode;
	static Alt alts[NALT+1];

	mc = arg;
	threadsetname("cookthread");

	alts[ACDOWN].c 	= cntdownc;
	alts[ACDOWN].v 	= nil;
	alts[ACDOWN].op	= CHANRCV;
	alts[ACOOK].c 	= cookc;
	alts[ACOOK].v 	= &m;
	alts[ACOOK].op	= CHANRCV;
	alts[AMODE].c	= modec;
	alts[AMODE].v	= &mode;
	alts[AMODE].op	= CHANRCV;
	alts[NALT].op	= CHANEND;

	for (;;) {
		switch(alt(alts)) {
			case ACDOWN:
				smdata.type = TIMER;
				smdata.now = msec();
				if (cooker(&smdata)) {
					send(mc->c, &(smdata.Mouse));
					/*
					 * mc->Mouse is updated after send so it doesn't have 
					 * wrong value if we block during send.
					 * This means that programs should receive from mc->Mouse 
					 * (see readmouse() above) if they want full synchrony.
					 */
					mc->Mouse = smdata.Mouse;
				}
				break;
			case ACOOK:
				smdata.type = MOUSE;
				smdata.Mouse = m;
				if (cooker(&smdata)) {
					send(mc->c, &(smdata.Mouse));
					mc->Mouse = smdata.Mouse;
				}
				break;
			case AMODE:
				mousemode = mode;
				send(modec, &mousemode);
				break;			
		} /* end of switch */
	} /* end of for */
}


static void
cntdownproc(void*)
{
	rfork(RFFDG);
	threadsetname("cntdownproc");
	recv(cntdownc, nil);
	for(;;) {
		sleep(TIMERSTEP);
		send(cntdownc, nil);
		recv(cntdownc, nil);
	}
}

/*
 * mode change do not care if the apication is
 * expecting for some MEND flag so take care when
 * you call setmousemode(MRAW) .
 */ 
int
setmousemode(Mousectl *mc, int mode)
{
	switch(mode) {
	case MCOOKED:
		consume(strlen(state));
		clean();
		/* create thread & proc on first call to cooked mode */
		if (cntdownc==nil) { 
			cntdownc = chancreate(sizeof(int), 0);
			cookc = chancreate(sizeof(Mouse), 0);
			proccreate(cntdownproc, nil, 8192);
			threadcreate(cookthread, mc, 8192);
		} /* fall through */
	case MRAW:
		send(modec, &mode);
		recv(modec, &mode);
		return(mode);
		break;
	default:
		return(-1);
	}
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.