Plan 9 from Bell Labs’s /usr/web/sources/plan9/sys/src/cmd/unix/drawterm/gui-x11/x11.c

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


#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"

#include <draw.h>
#include <memdraw.h>
#include <keyboard.h>
#include <cursor.h>
#include "screen.h"

#define argv0 "drawterm"

typedef struct Cursor Cursor;

#undef	long
#define	Font		XFont
#define	Screen	XScreen
#define	Display	XDisplay
#define	Cursor	XCursor

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/keysym.h>
#include "keysym2ucs.h"

#undef	Font
#undef	Screen
#undef	Display
#undef	Cursor
#define	long	int

/* perfect approximation to NTSC = .299r+.587g+.114b when 0 ≤ r,g,b < 256 */
#define RGB2K(r,g,b)	((156763*(r)+307758*(g)+59769*(b))>>19)

enum
{
	PMundef	= ~0		/* undefined pixmap id */
};

/*
 * Structure pointed to by X field of Memimage
 */
typedef struct Xmem Xmem;
struct Xmem
{
	int	pmid;	/* pixmap id for screen ldepth instance */
	XImage *xi;	/* local image if we currenty have the data */
	int	dirty;
	Rectangle dirtyr;
	Rectangle r;
	uintptr pc;	/* who wrote into xi */
};

static int	xgcfillcolor;
static int	xgcfillcolor0;
static int	xgcsimplecolor0;
static int	xgcsimplepm0;

static	XDisplay*	xdisplay;	/* used holding draw lock */
static int				xtblbit;
static int 			plan9tox11[256]; /* Values for mapping between */
static int 			x11toplan9[256]; /* X11 and Plan 9 */
static	GC		xgcfill, xgccopy, xgcsimplesrc, xgczero, xgcreplsrc;
static	GC		xgcfill0, xgccopy0, xgcsimplesrc0, xgczero0, xgcreplsrc0;
static	ulong	xscreenchan;
static	Drawable	xscreenid;
static	Visual		*xvis;

static int xdraw(Memdrawparam*);

#define glenda_width 48
#define glenda_height 48
static unsigned short glenda_bits[] = {
   0xffff, 0xffff, 0xffff, 0xffff, 0xffe9, 0xffff, 0x7fff, 0xffae, 0xffff,
   0xffff, 0xffbe, 0xffff, 0x1fff, 0xff3f, 0xffff, 0xbfff, 0xfe6e, 0xffff,
   0xbbff, 0xfcce, 0xffff, 0xffff, 0xf98c, 0xffff, 0xe5ff, 0xf31b, 0xffff,
   0x87ff, 0xe617, 0xffff, 0x05ff, 0xdf37, 0xffff, 0x0fff, 0x7ffe, 0xffff,
   0x1bff, 0xfffc, 0xfffa, 0x37ff, 0xfffc, 0xfffb, 0xd7ff, 0xfffc, 0xfff7,
   0xcfff, 0xffff, 0xfff7, 0xcfff, 0xffff, 0xffef, 0xdfff, 0xffff, 0xffef,
   0xafff, 0xffff, 0xffdf, 0xefff, 0xffff, 0xfff3, 0xdfff, 0xefff, 0xffd3,
   0xdfff, 0xc7ff, 0xffdf, 0xefff, 0xefff, 0xffef, 0xcfff, 0xffff, 0xffcf,
   0xdfff, 0xffff, 0xffd9, 0x9fff, 0x7fff, 0xffd0, 0xbfff, 0xffff, 0xffd7,
   0x7fff, 0xbfff, 0xffd0, 0x3fff, 0x3fff, 0xffd9, 0x7fff, 0x3fff, 0xffcb,
   0x3fff, 0xffff, 0xffdc, 0x3fff, 0xffff, 0xffdf, 0x3fff, 0xffff, 0xff9f,
   0x3fff, 0xffff, 0xffdf, 0x8fff, 0xffff, 0xff9f, 0xa7ff, 0xffff, 0xffdf,
   0xe3ff, 0xffff, 0xffcf, 0xe9ff, 0xffff, 0xffcf, 0xf1ff, 0xffff, 0xffef,
   0xf3ff, 0xffff, 0xffe7, 0xf9ff, 0xffff, 0xffe7, 0x53ff, 0xffff, 0xffe1,
   0x07ff, 0x7ffc, 0xffc6, 0x17ff, 0xeff0, 0xffee, 0xffff, 0xc781, 0xffe5,
   0xffff, 0x8807, 0xffe0, 0xffff, 0x003f, 0xfff0, 0xffff, 0x1fff, 0xfffe
};

/*
 * Synchronize images between X bitmaps and in-memory bitmaps.
 */
static void
addrect(Rectangle *rp, Rectangle r)
{
	if(rp->min.x >= rp->max.x)
		*rp = r;
	else
		combinerect(rp, r);
}

static XImage*
getXdata(Memimage *m, Rectangle r)
{
	uchar *p;
	int x, y;
	Xmem *xm;
	Point xdelta, delta;
	Point tp;

 	xm = m->X;
 	if(xm == nil)
 		return nil;
 
	assert(xm != nil && xm->xi != nil);
	
 	if(xm->dirty == 0)
 		return xm->xi;
 		
 	r = xm->dirtyr;
	if(Dx(r)==0 || Dy(r)==0)
		return xm->xi;

	delta = subpt(r.min, m->r.min);
	tp = xm->r.min;	/* avoid unaligned access on digital unix */
	xdelta = subpt(r.min, tp);
	
	XGetSubImage(xdisplay, xm->pmid, delta.x, delta.y, Dx(r), Dy(r),
		AllPlanes, ZPixmap, xm->xi, xdelta.x, xdelta.y);
		
	if(xtblbit && m->chan == CMAP8)
		for(y=r.min.y; y<r.max.y; y++)
			for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
				*p = x11toplan9[*p];
				
	xm->dirty = 0;
	xm->dirtyr = Rect(0,0,0,0);
	return xm->xi;
}

static void
putXdata(Memimage *m, Rectangle r)
{
	Xmem *xm;
	XImage *xi;
	GC g;
	Point xdelta, delta;
	Point tp;
	int x, y;
	uchar *p;

	xm = m->X;
	if(xm == nil)
		return;
		
	assert(xm != nil);
	assert(xm->xi != nil);

	xi = xm->xi;

	g = (m->chan == GREY1) ? xgccopy0 : xgccopy;

	delta = subpt(r.min, m->r.min);
	tp = xm->r.min;	/* avoid unaligned access on digital unix */
	xdelta = subpt(r.min, tp);
	
	if(xtblbit && m->chan == CMAP8)
		for(y=r.min.y; y<r.max.y; y++)
			for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
				*p = plan9tox11[*p];
	
	XPutImage(xdisplay, xm->pmid, g, xi, xdelta.x, xdelta.y, delta.x, delta.y, Dx(r), Dy(r));

	if(xtblbit && m->chan == CMAP8)
		for(y=r.min.y; y<r.max.y; y++)
			for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
				*p = x11toplan9[*p];
}

static void
dirtyXdata(Memimage *m, Rectangle r)
{
	Xmem *xm;
	
	if((xm = m->X) != nil){
		xm->dirty = 1;
		addrect(&xm->dirtyr, r);
	}
}

Memimage*
xallocmemimage(Rectangle r, ulong chan, int pmid)
{
	Memimage *m;
	Xmem *xm;
	XImage *xi;
	int offset;
	int d;
	
	m = _allocmemimage(r, chan);
	if(m == nil)
		return nil;
	if(chan != GREY1 && chan != xscreenchan)
		return m;

	d = m->depth;
	xm = mallocz(sizeof(Xmem), 1);
	if(pmid != PMundef)
		xm->pmid = pmid;
	else
		xm->pmid = XCreatePixmap(xdisplay, xscreenid, Dx(r), Dy(r), (d==32) ? 24 : d);
		
	if(m->depth == 24)
		offset = r.min.x&(4-1);
	else
		offset = r.min.x&(31/m->depth);
	r.min.x -= offset;
	
	assert(wordsperline(r, m->depth) <= m->width);

	xi = XCreateImage(xdisplay, xvis, m->depth==32?24:m->depth, ZPixmap, 0,
		(char*)m->data->bdata, Dx(r), Dy(r), 32, m->width*sizeof(ulong));
	
	if(xi == nil){
		_freememimage(m);
		return nil;
	}

	xm->xi = xi;
	xm->pc = getcallerpc(&r);
	xm->r = r;
	
	/*
	 * Set the parameters of the XImage so its memory looks exactly like a
	 * Memimage, so we can call _memimagedraw on the same data.  All frame
	 * buffers we've seen, and Plan 9's graphics code, require big-endian
	 * bits within bytes, but little endian byte order within pixels.
	 */
	xi->bitmap_unit = m->depth < 8 || m->depth == 24 ? 8 : m->depth;
	xi->byte_order = LSBFirst;
	xi->bitmap_bit_order = MSBFirst;
	xi->bitmap_pad = 32;
	xm->r = Rect(0,0,0,0);
	XInitImage(xi);
	XFlush(xdisplay);

	m->X = xm;
	return m;
}

void
xfillcolor(Memimage *m, Rectangle r, ulong v)
{
	GC gc;
	Xmem *dxm;

	dxm = m->X;
	assert(dxm != nil);
	r = rectsubpt(r, m->r.min);
		
	if(m->chan == GREY1){
		gc = xgcfill0;
		if(xgcfillcolor0 != v){
			XSetForeground(xdisplay, gc, v);
			xgcfillcolor0 = v;
		}
	}else{
		if(m->chan == CMAP8 && xtblbit)
			v = plan9tox11[v];
				
		gc = xgcfill;
		if(xgcfillcolor != v){
			XSetForeground(xdisplay, gc, v);
			xgcfillcolor = v;
		}
	}
	XFillRectangle(xdisplay, dxm->pmid, gc, r.min.x, r.min.y, Dx(r), Dy(r));
}

/*
 * Replacements for libmemdraw routines.
 * (They've been underscored.)
 */
Memimage*
allocmemimage(Rectangle r, ulong chan)
{
	return xallocmemimage(r, chan, PMundef);
}

void
freememimage(Memimage *m)
{
	Xmem *xm;
	
	if(m == nil)
		return;
		
	if(m->data->ref == 1){
		if((xm = m->X) != nil){
			if(xm->xi){
				xm->xi->data = nil;
				XFree(xm->xi);
			}
			XFreePixmap(xdisplay, xm->pmid);
			free(xm);
			m->X = nil;
		}
	}
	_freememimage(m);
}

void
memfillcolor(Memimage *m, ulong val)
{
	_memfillcolor(m, val);
	if(m->X){
		if((val & 0xFF) == 0xFF)
			xfillcolor(m, m->r, _rgbatoimg(m, val));
		else
			putXdata(m, m->r);
	}
}

int
loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
{
	int n;

	n = _loadmemimage(i, r, data, ndata);
	if(n > 0 && i->X)
		putXdata(i, r);
	return n;
}

int
cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
{
	int n;

	n = _cloadmemimage(i, r, data, ndata);
	if(n > 0 && i->X)
		putXdata(i, r);
	return n;
}

ulong
pixelbits(Memimage *m, Point p)
{
	if(m->X)
		getXdata(m, Rect(p.x, p.y, p.x+1, p.y+1));
	return _pixelbits(m, p);
}

void
memimageinit(void)
{
	static int didinit = 0;
	
	if(didinit)
		return;

	didinit = 1;
	_memimageinit();
	
	xfillcolor(memblack, memblack->r, 0);
	xfillcolor(memwhite, memwhite->r, 1);
}

void
memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp, Memimage *mask, Point mp, int op)
{
	Memdrawparam *par;
	
	if((par = _memimagedrawsetup(dst, r, src, sp, mask, mp, op)) == nil)
		return;
	_memimagedraw(par);
	if(!xdraw(par))
		putXdata(dst, par->r);
}

static int
xdraw(Memdrawparam *par)
{
	int dy, dx;
	unsigned m;
	Memimage *src, *dst, *mask;
	Xmem *dxm, *sxm, *mxm;
	GC gc;
	Rectangle r, sr, mr;
	ulong sdval;

	dx = Dx(par->r);
	dy = Dy(par->r);
	src = par->src;
	dst = par->dst;
	mask = par->mask;
	r = par->r;
	sr = par->sr;
	mr = par->mr;
	sdval = par->sdval;

	/*
	 * drawterm was distributed for years with
	 * "return 0;" right here.
	 * maybe we should give up on all this?
	 */

	if((dxm = dst->X) == nil)
		return 0;

	/*
	 * If we have an opaque mask and source is one opaque pixel we can convert to the
	 * destination format and just XFillRectangle.
	 */
	m = Simplesrc|Simplemask|Fullmask;
	if((par->state&m)==m){
		xfillcolor(dst, r, sdval);
		dirtyXdata(dst, par->r);
		return 1;
	}

	/*
	 * If no source alpha, an opaque mask, we can just copy the
	 * source onto the destination.  If the channels are the same and
	 * the source is not replicated, XCopyArea suffices.
	 */
	m = Simplemask|Fullmask;
	if((par->state&(m|Replsrc))==m && src->chan == dst->chan && src->X){
		sxm = src->X;
		r = rectsubpt(r, dst->r.min);		
		sr = rectsubpt(sr, src->r.min);
		if(dst->chan == GREY1)
			gc = xgccopy0;
		else
			gc = xgccopy;
		XCopyArea(xdisplay, sxm->pmid, dxm->pmid, gc, 
			sr.min.x, sr.min.y, dx, dy, r.min.x, r.min.y);
		dirtyXdata(dst, par->r);
		return 1;
	}
	
	/*
	 * If no source alpha, a 1-bit mask, and a simple source
	 * we can just copy through the mask onto the destination.
	 */
	if(dst->X && mask->X && !(mask->flags&Frepl)
	&& mask->chan == GREY1 && (par->state&Simplesrc)){
		Point p;

		mxm = mask->X;
		r = rectsubpt(r, dst->r.min);		
		mr = rectsubpt(mr, mask->r.min);
		p = subpt(r.min, mr.min);
		if(dst->chan == GREY1){
			gc = xgcsimplesrc0;
			if(xgcsimplecolor0 != sdval){
				XSetForeground(xdisplay, gc, sdval);
				xgcsimplecolor0 = sdval;
			}
			if(xgcsimplepm0 != mxm->pmid){
				XSetStipple(xdisplay, gc, mxm->pmid);
				xgcsimplepm0 = mxm->pmid;
			}
		}else{
		/* somehow this doesn't work on rob's mac 
			gc = xgcsimplesrc;
			if(dst->chan == CMAP8 && xtblbit)
				sdval = plan9tox11[sdval];
				
			if(xgcsimplecolor != sdval){
				XSetForeground(xdisplay, gc, sdval);
				xgcsimplecolor = sdval;
			}
			if(xgcsimplepm != mxm->pmid){
				XSetStipple(xdisplay, gc, mxm->pmid);
				xgcsimplepm = mxm->pmid;
			}
		*/
			return 0;
		}
		XSetTSOrigin(xdisplay, gc, p.x, p.y);
		XFillRectangle(xdisplay, dxm->pmid, gc, r.min.x, r.min.y, dx, dy);
		dirtyXdata(dst, par->r);
		return 1;
	}
	return 0;
}

/*
 * X11 window management and kernel hooks.
 * Oh, how I loathe this code!
 */

static XColor			map[256];	/* Plan 9 colormap array */
static XColor			map7[128];	/* Plan 9 colormap array */
static uchar			map7to8[128][2];
static Colormap		xcmap;		/* Default shared colormap  */

extern int mousequeue;

/* for copy/paste, lifted from plan9ports */
static Atom clipboard; 
static Atom utf8string;
static Atom targets;
static Atom text;
static Atom compoundtext;

static	Drawable	xdrawable;
static	void		xexpose(XEvent*);
static	void		xmouse(XEvent*);
static	void		xkeyboard(XEvent*);
static	void		xmapping(XEvent*);
static	void		xdestroy(XEvent*);
static	void		xselect(XEvent*, XDisplay*);
static	void		xproc(void*);
static	Memimage*		xinitscreen(void);
static	void		initmap(Window);
static	GC		creategc(Drawable);
static	void		graphicscmap(XColor*);
static	int		xscreendepth;
static	XDisplay*	xkmcon;	/* used only in xproc */
static	XDisplay*	xsnarfcon;	/* used holding clip.lk */
static	ulong		xblack;
static	ulong		xwhite;

static	int	putsnarf, assertsnarf;

	Memimage *gscreen;
	Screeninfo screen;

void
flushmemscreen(Rectangle r)
{
	assert(!drawcanqlock());
	if(r.min.x >= r.max.x || r.min.y >= r.max.y)
		return;
	XCopyArea(xdisplay, xscreenid, xdrawable, xgccopy, r.min.x, r.min.y, Dx(r), Dy(r), r.min.x, r.min.y);
	XFlush(xdisplay);
}

void
screeninit(void)
{
	_memmkcmap();

	gscreen = xinitscreen();
	kproc("xscreen", xproc, nil);

	memimageinit();
	terminit();
	drawqlock();
	flushmemscreen(gscreen->r);
	drawqunlock();
}

uchar*
attachscreen(Rectangle *r, ulong *chan, int *depth,
	int *width, int *softscreen, void **X)
{
	*r = gscreen->r;
	*chan = gscreen->chan;
	*depth = gscreen->depth;
	*width = gscreen->width;
	*X = gscreen->X;
	*softscreen = 1;

	return gscreen->data->bdata;
}

static int
revbyte(int b)
{
	int r;

	r = 0;
	r |= (b&0x01) << 7;
	r |= (b&0x02) << 5;
	r |= (b&0x04) << 3;
	r |= (b&0x08) << 1;
	r |= (b&0x10) >> 1;
	r |= (b&0x20) >> 3;
	r |= (b&0x40) >> 5;
	r |= (b&0x80) >> 7;
	return r;
}

void
mouseset(Point xy)
{
	drawqlock();
	XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, xy.x, xy.y);
	XFlush(xdisplay);
	drawqunlock();
}

static XCursor xcursor;

void
setcursor(void)
{
	XCursor xc;
	XColor fg, bg;
	Pixmap xsrc, xmask;
	int i;
	uchar src[2*16], mask[2*16];

	for(i=0; i<2*16; i++){
		src[i] = revbyte(cursor.set[i]);
		mask[i] = revbyte(cursor.set[i] | cursor.clr[i]);
	}

	drawqlock();
	fg = map[0];
	bg = map[255];
	xsrc = XCreateBitmapFromData(xdisplay, xdrawable, (char*)src, 16, 16);
	xmask = XCreateBitmapFromData(xdisplay, xdrawable, (char*)mask, 16, 16);
	xc = XCreatePixmapCursor(xdisplay, xsrc, xmask, &fg, &bg, -cursor.offset.x, -cursor.offset.y);
	if(xc != 0) {
		XDefineCursor(xdisplay, xdrawable, xc);
		if(xcursor != 0)
			XFreeCursor(xdisplay, xcursor);
		xcursor = xc;
	}
	XFreePixmap(xdisplay, xsrc);
	XFreePixmap(xdisplay, xmask);
	XFlush(xdisplay);
	drawqunlock();
}

void
cursorarrow(void)
{
	drawqlock();
	if(xcursor != 0){
		XFreeCursor(xdisplay, xcursor);
		xcursor = 0;
	}
	XUndefineCursor(xdisplay, xdrawable);
	XFlush(xdisplay);
	drawqunlock();
}

static void
xproc(void *arg)
{
	ulong mask;
	XEvent event;

	mask = 	KeyPressMask|
		ButtonPressMask|
		ButtonReleaseMask|
		PointerMotionMask|
		Button1MotionMask|
		Button2MotionMask|
		Button3MotionMask|
		Button4MotionMask|
		Button5MotionMask|
		ExposureMask|
		StructureNotifyMask;

	XSelectInput(xkmcon, xdrawable, mask);
	for(;;) {
		//XWindowEvent(xkmcon, xdrawable, mask, &event);
		XNextEvent(xkmcon, &event);
		xselect(&event, xkmcon);
		xkeyboard(&event);
		xmouse(&event);
		xexpose(&event);
		xmapping(&event);
		xdestroy(&event);
	}
}

static int
shutup(XDisplay *d, XErrorEvent *e)
{
	char buf[200];
	iprint("X error: error code=%d, request_code=%d, minor=%d\n", e->error_code, e->request_code, e->minor_code);
	XGetErrorText(d, e->error_code, buf, sizeof(buf));
	iprint("%s\n", buf);
	USED(d);
	USED(e);
	return 0;
}

static int
panicshutup(XDisplay *d)
{
	panic("x error");
	return -1;
}

static Memimage*
xinitscreen(void)
{
	Memimage *gscreen;
	int i, xsize, ysize, pmid;
	char *argv[2];
	char *disp_val;
	Window rootwin;
	Rectangle r;
	XWMHints hints;
	XScreen *screen;
	XVisualInfo xvi;
	int rootscreennum;
	XTextProperty name;
	XClassHint classhints;
	XSizeHints normalhints;
	XSetWindowAttributes attrs;
	XPixmapFormatValues *pfmt;
	int n;
	Pixmap icon_pixmap;

	xscreenid = 0;
	xdrawable = 0;

	xdisplay = XOpenDisplay(NULL);
	if(xdisplay == 0){
		iprint("xinitscreen: XOpenDisplay: %r [DISPLAY=%s]\n",
			getenv("DISPLAY"));
		exit(0);
	}

	XSetErrorHandler(shutup);
	XSetIOErrorHandler(panicshutup);
	rootscreennum = DefaultScreen(xdisplay);
	rootwin = DefaultRootWindow(xdisplay);
	
	xscreendepth = DefaultDepth(xdisplay, rootscreennum);
	if(XMatchVisualInfo(xdisplay, rootscreennum, 16, TrueColor, &xvi)
	|| XMatchVisualInfo(xdisplay, rootscreennum, 16, DirectColor, &xvi)){
		xvis = xvi.visual;
		xscreendepth = 16;
		xtblbit = 1;
	}
	else if(XMatchVisualInfo(xdisplay, rootscreennum, 24, TrueColor, &xvi)
	|| XMatchVisualInfo(xdisplay, rootscreennum, 24, DirectColor, &xvi)){
		xvis = xvi.visual;
		xscreendepth = 24;
		xtblbit = 1;
	}
	else if(XMatchVisualInfo(xdisplay, rootscreennum, 8, PseudoColor, &xvi)
	|| XMatchVisualInfo(xdisplay, rootscreennum, 8, StaticColor, &xvi)){
		if(xscreendepth > 8)
			panic("drawterm: can't deal with colormapped depth %d screens\n", xscreendepth);
		xvis = xvi.visual;
		xscreendepth = 8;
	}
	else{
		if(xscreendepth != 8)
			panic("drawterm: can't deal with depth %d screens\n", xscreendepth);
		xvis = DefaultVisual(xdisplay, rootscreennum);
	}

	/*
	 * xscreendepth is only the number of significant pixel bits,
	 * not the total.  We need to walk the display list to find
	 * how many actual bits are being used per pixel.
	 */
	xscreenchan = 0; /* not a valid channel */
	pfmt = XListPixmapFormats(xdisplay, &n);
	for(i=0; i<n; i++){
		if(pfmt[i].depth == xscreendepth){
			switch(pfmt[i].bits_per_pixel){
			case 1:	/* untested */
				xscreenchan = GREY1;
				break;
			case 2:	/* untested */
				xscreenchan = GREY2;
				break;
			case 4:	/* untested */
				xscreenchan = GREY4;
				break;
			case 8:
				xscreenchan = CMAP8;
				break;
			case 16: /* uses 16 rather than 15, empirically. */
				xscreenchan = RGB16;
				break;
			case 24: /* untested (impossible?) */
				xscreenchan = RGB24;
				break;
			case 32:
				xscreenchan = CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8);
				break;
			}
		}
	}
	if(xscreenchan == 0)
		panic("drawterm: unknown screen pixel format\n");
		
	screen = DefaultScreenOfDisplay(xdisplay);
	xcmap = DefaultColormapOfScreen(screen);

	if(xvis->class != StaticColor){
		graphicscmap(map);
		initmap(rootwin);
	}

	r.min = ZP;
	r.max.x = WidthOfScreen(screen);
	r.max.y = HeightOfScreen(screen);

	xsize = Dx(r)*3/4;
	ysize = Dy(r)*3/4;
	
	attrs.colormap = xcmap;
	attrs.background_pixel = 0;
	attrs.border_pixel = 0;
	/* attrs.override_redirect = 1;*/ /* WM leave me alone! |CWOverrideRedirect */
	xdrawable = XCreateWindow(xdisplay, rootwin, 0, 0, xsize, ysize, 0, 
		xscreendepth, InputOutput, xvis, CWBackPixel|CWBorderPixel|CWColormap, &attrs);

	/* load the given bitmap data and create an X pixmap containing it. */
	icon_pixmap = XCreateBitmapFromData(xdisplay,
		rootwin, (char *)glenda_bits,
		glenda_width, glenda_height);

	/*
	 * set up property as required by ICCCM
	 */
	name.value = (uchar*)"drawterm";
	name.encoding = XA_STRING;
	name.format = 8;
	name.nitems = strlen((char*)name.value);
	normalhints.flags = USSize|PMaxSize;
	normalhints.max_width = Dx(r);
	normalhints.max_height = Dy(r);
	normalhints.width = xsize;
	normalhints.height = ysize;
	hints.flags = IconPixmapHint |InputHint|StateHint;
	hints.input = 1;
	hints.initial_state = NormalState;
	hints.icon_pixmap = icon_pixmap;

	classhints.res_name = "drawterm";
	classhints.res_class = "Drawterm";
	argv[0] = "drawterm";
	argv[1] = nil;
	XSetWMProperties(xdisplay, xdrawable,
		&name,			/* XA_WM_NAME property for ICCCM */
		&name,			/* XA_WM_ICON_NAME */
		argv,			/* XA_WM_COMMAND */
		1,			/* argc */
		&normalhints,		/* XA_WM_NORMAL_HINTS */
		&hints,			/* XA_WM_HINTS */
		&classhints);		/* XA_WM_CLASS */
	XFlush(xdisplay);
	
	/*
	 * put the window on the screen
	 */
	XMapWindow(xdisplay, xdrawable);
	XFlush(xdisplay);

	xscreenid = XCreatePixmap(xdisplay, xdrawable, Dx(r), Dy(r), xscreendepth);
	gscreen = xallocmemimage(r, xscreenchan, xscreenid);
	
	xgcfill = creategc(xscreenid);
	XSetFillStyle(xdisplay, xgcfill, FillSolid);
	xgccopy = creategc(xscreenid);
	xgcsimplesrc = creategc(xscreenid);
	XSetFillStyle(xdisplay, xgcsimplesrc, FillStippled);
	xgczero = creategc(xscreenid);
	xgcreplsrc = creategc(xscreenid);
	XSetFillStyle(xdisplay, xgcreplsrc, FillTiled);

	pmid = XCreatePixmap(xdisplay, xdrawable, 1, 1, 1);
	xgcfill0 = creategc(pmid);
	XSetForeground(xdisplay, xgcfill0, 0);
	XSetFillStyle(xdisplay, xgcfill0, FillSolid);
	xgccopy0 = creategc(pmid);
	xgcsimplesrc0 = creategc(pmid);
	XSetFillStyle(xdisplay, xgcsimplesrc0, FillStippled);
	xgczero0 = creategc(pmid);
	xgcreplsrc0 = creategc(pmid);
	XSetFillStyle(xdisplay, xgcreplsrc0, FillTiled);
	XFreePixmap(xdisplay, pmid);

	XSetForeground(xdisplay, xgccopy, plan9tox11[0]);
	XFillRectangle(xdisplay, xscreenid, xgccopy, 0, 0, xsize, ysize);

	xkmcon = XOpenDisplay(NULL);
	if(xkmcon == 0){
		disp_val = getenv("DISPLAY");
		if(disp_val == 0)
			disp_val = "not set";
		iprint("drawterm: open %r, DISPLAY is %s\n", disp_val);
		exit(0);
	}
	xsnarfcon = XOpenDisplay(NULL);
	if(xsnarfcon == 0){
		disp_val = getenv("DISPLAY");
		if(disp_val == 0)
			disp_val = "not set";
		iprint("drawterm: open %r, DISPLAY is %s\n", disp_val);
		exit(0);
	}

	clipboard = XInternAtom(xkmcon, "CLIPBOARD", False);
	utf8string = XInternAtom(xkmcon, "UTF8_STRING", False);
	targets = XInternAtom(xkmcon, "TARGETS", False);
	text = XInternAtom(xkmcon, "TEXT", False);
	compoundtext = XInternAtom(xkmcon, "COMPOUND_TEXT", False);

	xblack = screen->black_pixel;
	xwhite = screen->white_pixel;
	return gscreen;
}

static void
graphicscmap(XColor *map)
{
	int r, g, b, cr, cg, cb, v, num, den, idx, v7, idx7;

	for(r=0; r!=4; r++) {
		for(g = 0; g != 4; g++) {
			for(b = 0; b!=4; b++) {
				for(v = 0; v!=4; v++) {
					den=r;
					if(g > den)
						den=g;
					if(b > den)
						den=b;
					/* divide check -- pick grey shades */
					if(den==0)
						cr=cg=cb=v*17;
					else {
						num=17*(4*den+v);
						cr=r*num/den;
						cg=g*num/den;
						cb=b*num/den;
					}
					idx = r*64 + v*16 + ((g*4 + b + v - r) & 15);
					map[idx].red = cr*0x0101;
					map[idx].green = cg*0x0101;
					map[idx].blue = cb*0x0101;
					map[idx].pixel = idx;
					map[idx].flags = DoRed|DoGreen|DoBlue;

					v7 = v >> 1;
					idx7 = r*32 + v7*16 + g*4 + b;
					if((v & 1) == v7){
						map7to8[idx7][0] = idx;
						if(den == 0) { 		/* divide check -- pick grey shades */
							cr = ((255.0/7.0)*v7)+0.5;
							cg = cr;
							cb = cr;
						}
						else {
							num=17*15*(4*den+v7*2)/14;
							cr=r*num/den;
							cg=g*num/den;
							cb=b*num/den;
						}
						map7[idx7].red = cr*0x0101;
						map7[idx7].green = cg*0x0101;
						map7[idx7].blue = cb*0x0101;
						map7[idx7].pixel = idx7;
						map7[idx7].flags = DoRed|DoGreen|DoBlue;
					}
					else
						map7to8[idx7][1] = idx;
				}
			}
		}
	}
}

/*
 * Initialize and install the drawterm colormap as a private colormap for this
 * application.  Drawterm gets the best colors here when it has the cursor focus.
 */  
static void 
initmap(Window w)
{
	XColor c;
	int i;
	ulong p, pp;
	char buf[30];

	if(xscreendepth <= 1)
		return;

	if(xscreendepth >= 24) {
		/* The pixel value returned from XGetPixel needs to
		 * be converted to RGB so we can call rgb2cmap()
		 * to translate between 24 bit X and our color. Unfortunately,
		 * the return value appears to be display server endian 
		 * dependant. Therefore, we run some heuristics to later
		 * determine how to mask the int value correctly.
		 * Yeah, I know we can look at xvis->byte_order but 
		 * some displays say MSB even though they run on LSB.
		 * Besides, this is more anal.
		 */
		if(xscreendepth != DefaultDepth(xdisplay, DefaultScreen(xdisplay)))
			xcmap = XCreateColormap(xdisplay, w, xvis, AllocNone);

		c = map[19];
		/* find out index into colormap for our RGB */
		if(!XAllocColor(xdisplay, xcmap, &c))
			panic("drawterm: screen-x11 can't alloc color");

		p  = c.pixel;
		pp = rgb2cmap((p>>16)&0xff,(p>>8)&0xff,p&0xff);
		if(pp!=map[19].pixel) {
			/* check if endian is other way */
			pp = rgb2cmap(p&0xff,(p>>8)&0xff,(p>>16)&0xff);
			if(pp!=map[19].pixel)
				panic("cannot detect x server byte order");
			switch(xscreenchan){
			case RGB24:
				xscreenchan = BGR24;
				break;
			case XRGB32:
				xscreenchan = XBGR32;
				break;
			default:
				panic("don't know how to byteswap channel %s", 
					chantostr(buf, xscreenchan));
				break;
			}
		}
	} else if(xvis->class == TrueColor || xvis->class == DirectColor) {
	} else if(xvis->class == PseudoColor) {
		if(xtblbit == 0){
			xcmap = XCreateColormap(xdisplay, w, xvis, AllocAll); 
			XStoreColors(xdisplay, xcmap, map, 256);
			for(i = 0; i < 256; i++) {
				plan9tox11[i] = i;
				x11toplan9[i] = i;
			}
		}
		else {
			for(i = 0; i < 128; i++) {
				c = map7[i];
				if(!XAllocColor(xdisplay, xcmap, &c)) {
					iprint("drawterm: can't alloc colors in default map, don't use -7\n");
					exit(0);
				}
				plan9tox11[map7to8[i][0]] = c.pixel;
				plan9tox11[map7to8[i][1]] = c.pixel;
				x11toplan9[c.pixel] = map7to8[i][0];
			}
		}
	}
	else
		panic("drawterm: unsupported visual class %d\n", xvis->class);
}

static void
xdestroy(XEvent *e)
{
	XDestroyWindowEvent *xe;
	if(e->type != DestroyNotify)
		return;
	xe = (XDestroyWindowEvent*)e;
	if(xe->window == xdrawable)
		exit(0);
}

static void
xmapping(XEvent *e)
{
	XMappingEvent *xe;

	if(e->type != MappingNotify)
		return;
	xe = (XMappingEvent*)e;
	USED(xe);
}


/*
 * Disable generation of GraphicsExpose/NoExpose events in the GC.
 */
static GC
creategc(Drawable d)
{
	XGCValues gcv;

	gcv.function = GXcopy;
	gcv.graphics_exposures = False;
	return XCreateGC(xdisplay, d, GCFunction|GCGraphicsExposures, &gcv);
}

static void
xexpose(XEvent *e)
{
	Rectangle r;
	XExposeEvent *xe;

	if(e->type != Expose)
		return;
	xe = (XExposeEvent*)e;
	r.min.x = xe->x;
	r.min.y = xe->y;
	r.max.x = xe->x + xe->width;
	r.max.y = xe->y + xe->height;
	drawflushr(r);
}

static void
xkeyboard(XEvent *e)
{
	KeySym k;

	/*
	 * I tried using XtGetActionKeysym, but it didn't seem to
	 * do case conversion properly
	 * (at least, with Xterminal servers and R4 intrinsics)
	 */
	if(e->xany.type != KeyPress)
		return;


	XLookupString((XKeyEvent*)e, NULL, 0, &k, NULL);

	if(k == XK_Multi_key || k == NoSymbol)
		return;
	if(k&0xFF00){
		switch(k){
		case XK_BackSpace:
		case XK_Tab:
		case XK_Escape:
		case XK_Delete:
		case XK_KP_0:
		case XK_KP_1:
		case XK_KP_2:
		case XK_KP_3:
		case XK_KP_4:
		case XK_KP_5:
		case XK_KP_6:
		case XK_KP_7:
		case XK_KP_8:
		case XK_KP_9:
		case XK_KP_Divide:
		case XK_KP_Multiply:
		case XK_KP_Subtract:
		case XK_KP_Add:
		case XK_KP_Decimal:
			k &= 0x7F;
			break;
		case XK_Linefeed:
			k = '\r';
			break;
		case XK_KP_Space:
			k = ' ';
			break;
		case XK_Home:
		case XK_KP_Home:
			k = Khome;
			break;
		case XK_Left:
		case XK_KP_Left:
			k = Kleft;
			break;
		case XK_Up:
		case XK_KP_Up:
			k = Kup;
			break;
		case XK_Down:
		case XK_KP_Down:
			k = Kdown;
			break;
		case XK_Right:
		case XK_KP_Right:
			k = Kright;
			break;
		case XK_Page_Down:
		case XK_KP_Page_Down:
			k = Kpgdown;
			break;
		case XK_End:
		case XK_KP_End:
			k = Kend;
			break;
		case XK_Page_Up:	
		case XK_KP_Page_Up:
			k = Kpgup;
			break;
		case XK_Insert:
		case XK_KP_Insert:
			k = Kins;
			break;
		case XK_KP_Enter:
		case XK_Return:
			k = '\n';
			break;
		case XK_Alt_L:
		case XK_Alt_R:
			k = Kalt;
			break;
		case XK_F1:
		case XK_F2:
		case XK_F3:
		case XK_F4:
		case XK_F5:
		case XK_F6:
		case XK_F7:
		case XK_F8:
		case XK_F9:
		case XK_F10:
		case XK_F11:
		case XK_F12:
			k = KF|(k - XK_F1 + 1);
			break;
		case XK_Shift_L:
		case XK_Shift_R:
		case XK_Control_L:
		case XK_Control_R:
		case XK_Caps_Lock:
		case XK_Shift_Lock:

		case XK_Meta_L:
		case XK_Meta_R:
		case XK_Super_L:
		case XK_Super_R:
		case XK_Hyper_L:
		case XK_Hyper_R:
			return;
		default:		/* not ISO-1 or tty control */
  			if(k>0xff){
				k = keysym2ucs(k); /* supplied by X */
				if(k == -1)
					return;
			}
			break;
		}
	}

	/* Compensate for servers that call a minus a hyphen */
	if(k == XK_hyphen)
		k = XK_minus;
	/* Do control mapping ourselves if translator doesn't */
	if(e->xkey.state&ControlMask && k != Kalt)
		k &= 0x9f;
	if(k == NoSymbol) {
		return;
	}

	kbdputc(kbdq, k);
}

static void
xmouse(XEvent *e)
{
	Mousestate ms;
	int i, s;
	XButtonEvent *be;
	XMotionEvent *me;

	if(putsnarf != assertsnarf){
		assertsnarf = putsnarf;
		XSetSelectionOwner(xkmcon, XA_PRIMARY, xdrawable, CurrentTime);
		if(clipboard != None)
			XSetSelectionOwner(xkmcon, clipboard, xdrawable, CurrentTime);
		XFlush(xkmcon);
	}

	switch(e->type){
	case ButtonPress:
		be = (XButtonEvent *)e;
		/* 
		 * Fake message, just sent to make us announce snarf.
		 * Apparently state and button are 16 and 8 bits on
		 * the wire, since they are truncated by the time they
		 * get to us.
		 */
		if(be->send_event
		&& (~be->state&0xFFFF)==0
		&& (~be->button&0xFF)==0)
			return;
		ms.xy.x = be->x;
		ms.xy.y = be->y;
		s = be->state;
		ms.msec = be->time;
		switch(be->button){
		case 1:
			s |= Button1Mask;
			break;
		case 2:
			s |= Button2Mask;
			break;
		case 3:
			s |= Button3Mask;
			break;
		case 4:
			s |= Button4Mask;
			break;
		case 5:
			s |= Button5Mask;
			break;
		}
		break;
	case ButtonRelease:
		be = (XButtonEvent *)e;
		ms.xy.x = be->x;
		ms.xy.y = be->y;
		ms.msec = be->time;
		s = be->state;
		switch(be->button){
		case 1:
			s &= ~Button1Mask;
			break;
		case 2:
			s &= ~Button2Mask;
			break;
		case 3:
			s &= ~Button3Mask;
			break;
		case 4:
			s &= ~Button4Mask;
			break;
		case 5:
			s &= ~Button5Mask;
			break;
		}
		break;
	case MotionNotify:
		me = (XMotionEvent *)e;
		s = me->state;
		ms.xy.x = me->x;
		ms.xy.y = me->y;
		ms.msec = me->time;
		break;
	default:
		return;
	}

	ms.buttons = 0;
	if(s & Button1Mask)
		ms.buttons |= 1;
	if(s & Button2Mask)
		ms.buttons |= 2;
	if(s & Button3Mask)
		ms.buttons |= 4;
	if(s & Button4Mask)
		ms.buttons |= 8;
	if(s & Button5Mask)
		ms.buttons |= 16;

	lock(&mouse.lk);
	i = mouse.wi;
	if(mousequeue) {
		if(i == mouse.ri || mouse.lastb != ms.buttons || mouse.trans) {
			mouse.wi = (i+1)%Mousequeue;
			if(mouse.wi == mouse.ri)
				mouse.ri = (mouse.ri+1)%Mousequeue;
			mouse.trans = mouse.lastb != ms.buttons;
		} else {
			i = (i-1+Mousequeue)%Mousequeue;
		}
	} else {
		mouse.wi = (i+1)%Mousequeue;
		mouse.ri = i;
	}
	mouse.queue[i] = ms;
	mouse.lastb = ms.buttons;
	unlock(&mouse.lk);
	wakeup(&mouse.r);
}

void
getcolor(ulong i, ulong *r, ulong *g, ulong *b)
{
	ulong v;
	
	v = cmap2rgb(i);
	*r = (v>>16)&0xFF;
	*g = (v>>8)&0xFF;
	*b = v&0xFF;
}

void
setcolor(ulong i, ulong r, ulong g, ulong b)
{
	/* no-op */
}

int
atlocalconsole(void)
{
	char *p, *q;
	char buf[128];

	p = getenv("DRAWTERM_ATLOCALCONSOLE");
	if(p && atoi(p) == 1)
		return 1;

	p = getenv("DISPLAY");
	if(p == nil)
		return 0;

	/* extract host part */
	q = strchr(p, ':');
	if(q == nil)
		return 0;
	*q = 0;

	if(strcmp(p, "") == 0)
		return 1;

	/* try to match against system name (i.e. for ssh) */
	if(gethostname(buf, sizeof buf) == 0){
		if(strcmp(p, buf) == 0)
			return 1;
		if(strncmp(p, buf, strlen(p)) == 0 && buf[strlen(p)]=='.')
			return 1;
	}

	return 0;
}

/*
 * Cut and paste.  Just couldn't stand to make this simple...
 */

typedef struct Clip Clip;
struct Clip
{
	char buf[SnarfSize];
	QLock lk;
};
Clip clip;

#undef long	/* sic */
#undef ulong

static char*
_xgetsnarf(XDisplay *xd)
{
	uchar *data, *xdata;
	Atom clipboard, type, prop;
	unsigned long lastlen;
	unsigned long dummy, len;
	int fmt, i;
	Window w;

	qlock(&clip.lk);
	/*
	 * Have we snarfed recently and the X server hasn't caught up?
	 */
	if(putsnarf != assertsnarf)
		goto mine;

	/*
	 * Is there a primary selection (highlighted text in an xterm)?
	 */
	clipboard = XA_PRIMARY;
	w = XGetSelectionOwner(xd, XA_PRIMARY);
	if(w == xdrawable){
	mine:
		data = (uchar*)strdup(clip.buf);
		goto out;
	}

	/*
	 * If not, is there a clipboard selection?
	 */
	if(w == None && clipboard != None){
		clipboard = clipboard;
		w = XGetSelectionOwner(xd, clipboard);
		if(w == xdrawable)
			goto mine;
	}

	/*
	 * If not, give up.
	 */
	if(w == None){
		data = nil;
		goto out;
	}
		
	/*
	 * We should be waiting for SelectionNotify here, but it might never
	 * come, and we have no way to time out.  Instead, we will clear
	 * local property #1, request our buddy to fill it in for us, and poll
	 * until he's done or we get tired of waiting.
	 *
	 * We should try to go for utf8string instead of XA_STRING,
	 * but that would add to the polling.
	 */
	prop = 1;
	XChangeProperty(xd, xdrawable, prop, XA_STRING, 8, PropModeReplace, (uchar*)"", 0);
	XConvertSelection(xd, clipboard, XA_STRING, prop, xdrawable, CurrentTime);
	XFlush(xd);
	lastlen = 0;
	for(i=0; i<10 || (lastlen!=0 && i<30); i++){
		usleep(100*1000);
		XGetWindowProperty(xd, xdrawable, prop, 0, 0, 0, AnyPropertyType,
			&type, &fmt, &dummy, &len, &data);
		if(lastlen == len && len > 0)
			break;
		lastlen = len;
	}
	if(i == 10){
		data = nil;
		goto out;
	}
	/* get the property */
	data = nil;
	XGetWindowProperty(xd, xdrawable, prop, 0, SnarfSize/sizeof(unsigned long), 0, 
		AnyPropertyType, &type, &fmt, &len, &dummy, &xdata);
	if((type != XA_STRING && type != utf8string) || len == 0){
		if(xdata)
			XFree(xdata);
		data = nil;
	}else{
		if(xdata){
			data = (uchar*)strdup((char*)xdata);
			XFree(xdata);
		}else
			data = nil;
	}
out:
	qunlock(&clip.lk);
	return (char*)data;
}

static void
_xputsnarf(XDisplay *xd, char *data)
{
	XButtonEvent e;

	if(strlen(data) >= SnarfSize)
		return;
	qlock(&clip.lk);
	strcpy(clip.buf, data);

	/* leave note for mouse proc to assert selection ownership */
	putsnarf++;

	/* send mouse a fake event so snarf is announced */
	memset(&e, 0, sizeof e);
	e.type = ButtonPress;
	e.window = xdrawable;
	e.state = ~0;
	e.button = ~0;
	XSendEvent(xd, xdrawable, True, ButtonPressMask, (XEvent*)&e);
	XFlush(xd);
	qunlock(&clip.lk);
}

static void
xselect(XEvent *e, XDisplay *xd)
{
	char *name;
	XEvent r;
	XSelectionRequestEvent *xe;
	Atom a[4];

	if(e->xany.type != SelectionRequest)
		return;

	memset(&r, 0, sizeof r);
	xe = (XSelectionRequestEvent*)e;
if(0) iprint("xselect target=%d requestor=%d property=%d selection=%d\n",
	xe->target, xe->requestor, xe->property, xe->selection);
	r.xselection.property = xe->property;
	if(xe->target == targets){
		a[0] = XA_STRING;
		a[1] = utf8string;
		a[2] = text;
		a[3] = compoundtext;

		XChangeProperty(xd, xe->requestor, xe->property, XA_ATOM,
			32, PropModeReplace, (uchar*)a, sizeof a);
	}else if(xe->target == XA_STRING || xe->target == utf8string || xe->target == text || xe->target == compoundtext){
	text:
		/* if the target is STRING we're supposed to reply with Latin1 XXX */
		qlock(&clip.lk);
		XChangeProperty(xd, xe->requestor, xe->property, xe->target,
			8, PropModeReplace, (uchar*)clip.buf, strlen(clip.buf));
		qunlock(&clip.lk);
	}else{
		name = XGetAtomName(xd, xe->target);
		if(name == nil)
			iprint("XGetAtomName %d failed\n", xe->target);
		if(name){
			if(strcmp(name, "TIMESTAMP") == 0){
				/* nothing */
			}else if(strncmp(name, "image/", 6) == 0){
				/* nothing */
			}else if(strcmp(name, "text/html") == 0){
				/* nothing */
			}else if(strcmp(name, "text/plain") == 0 || strcmp(name, "text/plain;charset=UTF-8") == 0){
				goto text;
			}else
				iprint("%s: cannot handle selection request for '%s' (%d)\n", argv0, name, (int)xe->target);
		}
		r.xselection.property = None;
	}

	r.xselection.display = xe->display;
	/* r.xselection.property filled above */
	r.xselection.target = xe->target;
	r.xselection.type = SelectionNotify;
	r.xselection.requestor = xe->requestor;
	r.xselection.time = xe->time;
	r.xselection.send_event = True;
	r.xselection.selection = xe->selection;
	XSendEvent(xd, xe->requestor, False, 0, &r);
	XFlush(xd);
}

char*
clipread(void)
{
	return _xgetsnarf(xsnarfcon);
}

int
clipwrite(char *buf)
{
	_xputsnarf(xsnarfcon, buf);
	return 0;
}


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.