| /* |
| * Window system protocol server. |
| * Use select and a single proc and single stack |
| * to avoid aggravating the X11 library, which is |
| * subtle and quick to anger. |
| */ |
| |
| // #define SHOWEVENT |
| |
| #include <u.h> |
| #include <sys/select.h> |
| #include <errno.h> |
| #ifdef SHOWEVENT |
| #include <stdio.h> |
| #endif |
| #include "x11-inc.h" |
| |
| #include <libc.h> |
| #include <draw.h> |
| #include <memdraw.h> |
| #include <memlayer.h> |
| #include <keyboard.h> |
| #include <mouse.h> |
| #include <cursor.h> |
| #include <drawfcall.h> |
| #include "x11-memdraw.h" |
| #include "devdraw.h" |
| |
| #undef time |
| |
| #define MouseMask (\ |
| ButtonPressMask|\ |
| ButtonReleaseMask|\ |
| PointerMotionMask|\ |
| Button1MotionMask|\ |
| Button2MotionMask|\ |
| Button3MotionMask) |
| |
| #define Mask MouseMask|ExposureMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask|EnterWindowMask|LeaveWindowMask|FocusChangeMask |
| |
| typedef struct Kbdbuf Kbdbuf; |
| typedef struct Mousebuf Mousebuf; |
| typedef struct Fdbuf Fdbuf; |
| typedef struct Tagbuf Tagbuf; |
| |
| struct Kbdbuf |
| { |
| Rune r[32]; |
| int ri; |
| int wi; |
| int stall; |
| }; |
| |
| struct Mousebuf |
| { |
| Mouse m[32]; |
| int ri; |
| int wi; |
| int stall; |
| int resized; |
| }; |
| |
| struct Tagbuf |
| { |
| int t[32]; |
| int ri; |
| int wi; |
| }; |
| |
| struct Fdbuf |
| { |
| uchar buf[2*MAXWMSG]; |
| uchar *rp; |
| uchar *wp; |
| uchar *ep; |
| }; |
| |
| Kbdbuf kbd; |
| Mousebuf mouse; |
| Fdbuf fdin; |
| Fdbuf fdout; |
| Tagbuf kbdtags; |
| Tagbuf mousetags; |
| |
| void fdslide(Fdbuf*); |
| void runmsg(Wsysmsg*); |
| void replymsg(Wsysmsg*); |
| void runxevent(XEvent*); |
| void matchkbd(void); |
| void matchmouse(void); |
| int fdnoblock(int); |
| |
| int chatty; |
| int drawsleep; |
| int fullscreen; |
| |
| Rectangle windowrect; |
| Rectangle screenrect; |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: devdraw (don't run directly)\n"); |
| exits("usage"); |
| } |
| |
| void |
| bell(void *v, char *msg) |
| { |
| if(strcmp(msg, "alarm") == 0) |
| drawsleep = drawsleep ? 0 : 1000; |
| noted(NCONT); |
| } |
| |
| void |
| main(int argc, char **argv) |
| { |
| int n, top, firstx; |
| fd_set rd, wr, xx; |
| Wsysmsg m; |
| XEvent event; |
| |
| /* |
| * Move the protocol off stdin/stdout so that |
| * any inadvertent prints don't screw things up. |
| */ |
| dup(0, 3); |
| dup(1, 4); |
| close(0); |
| close(1); |
| open("/dev/null", OREAD); |
| open("/dev/null", OWRITE); |
| |
| /* reopens stdout if debugging */ |
| runxevent(0); |
| |
| fmtinstall('W', drawfcallfmt); |
| |
| ARGBEGIN{ |
| case 'D': |
| chatty++; |
| break; |
| default: |
| usage(); |
| }ARGEND |
| |
| /* |
| * Ignore arguments. They're only for good ps -a listings. |
| */ |
| |
| notify(bell); |
| |
| fdin.rp = fdin.wp = fdin.buf; |
| fdin.ep = fdin.buf+sizeof fdin.buf; |
| |
| fdout.rp = fdout.wp = fdout.buf; |
| fdout.ep = fdout.buf+sizeof fdout.buf; |
| |
| fdnoblock(3); |
| fdnoblock(4); |
| |
| firstx = 1; |
| _x.fd = -1; |
| for(;;){ |
| /* set up file descriptors */ |
| FD_ZERO(&rd); |
| FD_ZERO(&wr); |
| FD_ZERO(&xx); |
| /* |
| * Don't read unless there's room *and* we haven't |
| * already filled the output buffer too much. |
| */ |
| if(fdout.wp < fdout.buf+MAXWMSG && fdin.wp < fdin.ep) |
| FD_SET(3, &rd); |
| if(fdout.wp > fdout.rp) |
| FD_SET(4, &wr); |
| FD_SET(3, &xx); |
| FD_SET(4, &xx); |
| top = 4; |
| if(_x.fd >= 0){ |
| if(firstx){ |
| firstx = 0; |
| XSelectInput(_x.display, _x.drawable, Mask); |
| } |
| FD_SET(_x.fd, &rd); |
| FD_SET(_x.fd, &xx); |
| XFlush(_x.display); |
| if(_x.fd > top) |
| top = _x.fd; |
| } |
| |
| if(chatty) |
| fprint(2, "select %d...\n", top+1); |
| /* wait for something to happen */ |
| again: |
| if(select(top+1, &rd, &wr, &xx, NULL) < 0){ |
| if(errno == EINTR) |
| goto again; |
| if(chatty) |
| fprint(2, "select failure\n"); |
| exits(0); |
| } |
| if(chatty) |
| fprint(2, "got select...\n"); |
| |
| { |
| /* read what we can */ |
| n = 1; |
| while(fdin.wp < fdin.ep && (n = read(3, fdin.wp, fdin.ep-fdin.wp)) > 0) |
| fdin.wp += n; |
| if(n == 0){ |
| if(chatty) |
| fprint(2, "eof\n"); |
| exits(0); |
| } |
| if(n < 0 && errno != EAGAIN) |
| sysfatal("reading wsys msg: %r"); |
| |
| /* pick off messages one by one */ |
| while((n = convM2W(fdin.rp, fdin.wp-fdin.rp, &m)) > 0){ |
| /* fprint(2, "<- %W\n", &m); */ |
| runmsg(&m); |
| fdin.rp += n; |
| } |
| |
| /* slide data to beginning of buf */ |
| fdslide(&fdin); |
| } |
| { |
| /* write what we can */ |
| n = 1; |
| while(fdout.rp < fdout.wp && (n = write(4, fdout.rp, fdout.wp-fdout.rp)) > 0) |
| fdout.rp += n; |
| if(n == 0) |
| sysfatal("short write writing wsys"); |
| if(n < 0 && errno != EAGAIN) |
| sysfatal("writing wsys msg: %r"); |
| |
| /* slide data to beginning of buf */ |
| fdslide(&fdout); |
| } |
| { |
| /* |
| * Read an X message if we can. |
| * (XPending actually calls select to make sure |
| * the display's fd is readable and then reads |
| * in any waiting data before declaring whether |
| * there are events on the queue.) |
| */ |
| while(XPending(_x.display)){ |
| XNextEvent(_x.display, &event); |
| runxevent(&event); |
| } |
| } |
| } |
| } |
| |
| int |
| fdnoblock(int fd) |
| { |
| return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK); |
| } |
| |
| void |
| fdslide(Fdbuf *fb) |
| { |
| int n; |
| |
| n = fb->wp - fb->rp; |
| if(n > 0) |
| memmove(fb->buf, fb->rp, n); |
| fb->rp = fb->buf; |
| fb->wp = fb->rp+n; |
| } |
| |
| void |
| replyerror(Wsysmsg *m) |
| { |
| char err[256]; |
| |
| rerrstr(err, sizeof err); |
| m->type = Rerror; |
| m->error = err; |
| replymsg(m); |
| } |
| |
| |
| |
| /* |
| * Handle a single wsysmsg. |
| * Might queue for later (kbd, mouse read) |
| */ |
| void |
| runmsg(Wsysmsg *m) |
| { |
| uchar buf[65536]; |
| int n; |
| Memimage *i; |
| |
| switch(m->type){ |
| case Tinit: |
| memimageinit(); |
| i = _xattach(m->label, m->winsize); |
| _initdisplaymemimage(i); |
| replymsg(m); |
| break; |
| |
| case Trdmouse: |
| mousetags.t[mousetags.wi++] = m->tag; |
| if(mousetags.wi == nelem(mousetags.t)) |
| mousetags.wi = 0; |
| if(mousetags.wi == mousetags.ri) |
| sysfatal("too many queued mouse reads"); |
| /* fprint(2, "mouse unstall\n"); */ |
| mouse.stall = 0; |
| matchmouse(); |
| break; |
| |
| case Trdkbd: |
| kbdtags.t[kbdtags.wi++] = m->tag; |
| if(kbdtags.wi == nelem(kbdtags.t)) |
| kbdtags.wi = 0; |
| if(kbdtags.wi == kbdtags.ri) |
| sysfatal("too many queued keyboard reads"); |
| kbd.stall = 0; |
| matchkbd(); |
| break; |
| |
| case Tmoveto: |
| _xmoveto(m->mouse.xy); |
| replymsg(m); |
| break; |
| |
| case Tcursor: |
| if(m->arrowcursor) |
| _xsetcursor(nil); |
| else |
| _xsetcursor(&m->cursor); |
| replymsg(m); |
| break; |
| |
| case Tbouncemouse: |
| _xbouncemouse(&m->mouse); |
| replymsg(m); |
| break; |
| |
| case Tlabel: |
| _xsetlabel(m->label); |
| replymsg(m); |
| break; |
| |
| case Trdsnarf: |
| m->snarf = _xgetsnarf(); |
| replymsg(m); |
| free(m->snarf); |
| break; |
| |
| case Twrsnarf: |
| _xputsnarf(m->snarf); |
| replymsg(m); |
| break; |
| |
| case Trddraw: |
| n = m->count; |
| if(n > sizeof buf) |
| n = sizeof buf; |
| n = _drawmsgread(buf, n); |
| if(n < 0) |
| replyerror(m); |
| else{ |
| m->count = n; |
| m->data = buf; |
| replymsg(m); |
| } |
| break; |
| |
| case Twrdraw: |
| if(_drawmsgwrite(m->data, m->count) < 0) |
| replyerror(m); |
| else |
| replymsg(m); |
| break; |
| |
| case Ttop: |
| _xtopwindow(); |
| replymsg(m); |
| break; |
| |
| case Tresize: |
| _xresizewindow(m->rect); |
| replymsg(m); |
| break; |
| } |
| } |
| |
| /* |
| * Reply to m. |
| */ |
| void |
| replymsg(Wsysmsg *m) |
| { |
| int n; |
| |
| /* T -> R msg */ |
| if(m->type%2 == 0) |
| m->type++; |
| |
| /* fprint(2, "-> %W\n", m); */ |
| /* copy to output buffer */ |
| n = sizeW2M(m); |
| if(fdout.wp+n > fdout.ep) |
| sysfatal("out of space for reply message"); |
| convW2M(m, fdout.wp, n); |
| fdout.wp += n; |
| } |
| |
| /* |
| * Match queued kbd reads with queued kbd characters. |
| */ |
| void |
| matchkbd(void) |
| { |
| Wsysmsg m; |
| |
| if(kbd.stall) |
| return; |
| while(kbd.ri != kbd.wi && kbdtags.ri != kbdtags.wi){ |
| m.type = Rrdkbd; |
| m.tag = kbdtags.t[kbdtags.ri++]; |
| if(kbdtags.ri == nelem(kbdtags.t)) |
| kbdtags.ri = 0; |
| m.rune = kbd.r[kbd.ri++]; |
| if(kbd.ri == nelem(kbd.r)) |
| kbd.ri = 0; |
| replymsg(&m); |
| } |
| } |
| |
| /* |
| * Match queued mouse reads with queued mouse events. |
| */ |
| void |
| matchmouse(void) |
| { |
| Wsysmsg m; |
| |
| while(mouse.ri != mouse.wi && mousetags.ri != mousetags.wi){ |
| m.type = Rrdmouse; |
| m.tag = mousetags.t[mousetags.ri++]; |
| if(mousetags.ri == nelem(mousetags.t)) |
| mousetags.ri = 0; |
| m.mouse = mouse.m[mouse.ri]; |
| m.resized = mouse.resized; |
| /* |
| if(m.resized) |
| fprint(2, "sending resize\n"); |
| */ |
| mouse.resized = 0; |
| mouse.ri++; |
| if(mouse.ri == nelem(mouse.m)) |
| mouse.ri = 0; |
| replymsg(&m); |
| } |
| } |
| |
| static int kbuttons; |
| static int altdown; |
| static int kstate; |
| |
| static void |
| sendmouse(Mouse m) |
| { |
| m.buttons |= kbuttons; |
| mouse.m[mouse.wi] = m; |
| mouse.wi++; |
| if(mouse.wi == nelem(mouse.m)) |
| mouse.wi = 0; |
| if(mouse.wi == mouse.ri){ |
| mouse.stall = 1; |
| mouse.ri = 0; |
| mouse.wi = 1; |
| mouse.m[0] = m; |
| /* fprint(2, "mouse stall\n"); */ |
| } |
| matchmouse(); |
| } |
| |
| /* |
| * Handle an incoming X event. |
| */ |
| void |
| runxevent(XEvent *xev) |
| { |
| int c; |
| KeySym k; |
| static Mouse m; |
| XButtonEvent *be; |
| XKeyEvent *ke; |
| |
| #ifdef SHOWEVENT |
| static int first = 1; |
| if(first){ |
| dup(create("/tmp/devdraw.out", OWRITE, 0666), 1); |
| setbuf(stdout, 0); |
| first = 0; |
| } |
| #endif |
| |
| if(xev == 0) |
| return; |
| |
| #ifdef SHOWEVENT |
| print("\n"); |
| ShowEvent(xev); |
| #endif |
| |
| switch(xev->type){ |
| case Expose: |
| _xexpose(xev); |
| break; |
| |
| case DestroyNotify: |
| if(_xdestroy(xev)) |
| exits(0); |
| break; |
| |
| case ConfigureNotify: |
| if(_xconfigure(xev)){ |
| mouse.resized = 1; |
| _xreplacescreenimage(); |
| sendmouse(m); |
| } |
| break; |
| |
| case ButtonPress: |
| be = (XButtonEvent*)xev; |
| if(be->button == 1) { |
| if(kstate & ControlMask) |
| be->button = 2; |
| else if(kstate & Mod1Mask) |
| be->button = 3; |
| } |
| // fall through |
| case ButtonRelease: |
| altdown = 0; |
| // fall through |
| case MotionNotify: |
| if(mouse.stall) |
| return; |
| if(_xtoplan9mouse(xev, &m) < 0) |
| return; |
| sendmouse(m); |
| break; |
| |
| case KeyRelease: |
| case KeyPress: |
| ke = (XKeyEvent*)xev; |
| XLookupString(ke, NULL, 0, &k, NULL); |
| c = ke->state; |
| switch(k) { |
| case XK_Alt_L: |
| case XK_Meta_L: /* Shift Alt on PCs */ |
| case XK_Alt_R: |
| case XK_Meta_R: /* Shift Alt on PCs */ |
| case XK_Multi_key: |
| if(xev->type == KeyPress) |
| altdown = 1; |
| else if(altdown) { |
| altdown = 0; |
| sendalt(); |
| } |
| break; |
| } |
| |
| switch(k) { |
| case XK_Control_L: |
| if(xev->type == KeyPress) |
| c |= ControlMask; |
| else |
| c &= ~ControlMask; |
| goto kbutton; |
| case XK_Alt_L: |
| case XK_Shift_L: |
| if(xev->type == KeyPress) |
| c |= Mod1Mask; |
| else |
| c &= ~Mod1Mask; |
| kbutton: |
| kstate = c; |
| if(m.buttons || kbuttons) { |
| altdown = 0; // used alt |
| kbuttons = 0; |
| if(c & ControlMask) |
| kbuttons |= 2; |
| if(c & Mod1Mask) |
| kbuttons |= 4; |
| sendmouse(m); |
| break; |
| } |
| } |
| |
| if(xev->type != KeyPress) |
| break; |
| if(k == XK_F11){ |
| fullscreen = !fullscreen; |
| _xmovewindow(fullscreen ? screenrect : windowrect); |
| return; |
| } |
| if(kbd.stall) |
| return; |
| if((c = _xtoplan9kbd(xev)) < 0) |
| return; |
| kbd.r[kbd.wi++] = c; |
| if(kbd.wi == nelem(kbd.r)) |
| kbd.wi = 0; |
| if(kbd.ri == kbd.wi) |
| kbd.stall = 1; |
| matchkbd(); |
| break; |
| |
| case FocusOut: |
| /* |
| * Some key combinations (e.g. Alt-Tab) can cause us |
| * to see the key down event without the key up event, |
| * so clear out the keyboard state when we lose the focus. |
| */ |
| kstate = 0; |
| altdown = 0; |
| abortcompose(); |
| break; |
| |
| case SelectionRequest: |
| _xselect(xev); |
| break; |
| } |
| } |
| |