#include #include #include #include #include #include #include /* 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;imskold; 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=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;jMouse; 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;jpreffix,state,&fl); matchall=(lmatch==slen)&&(lmatch>0); yetnotmatch=(lmatch==IISPREFP); matchpref=(lmatch0); 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); } }