blob: 17563f411a521f671b3bfd77194a0d6d99defc96 [file] [log] [blame]
/*
* 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;
}
}