/*
* This implementation of the screen functions for X11 uses the
* portable implementation of the Inferno drawing operations (libmemdraw)
* to do the work, then has flushmemscreen copy the result to the X11 display.
* Thus it potentially supports all colour depths but with a possible
* performance penalty (although it tries to use the X11 shared memory extension
* to copy the result to the screen, which might reduce the latter).
*
* CraigN
*/
#define _GNU_SOURCE 1
#include "dat.h"
#include "fns.h"
#undef log2
#include <draw.h>
#include "cursor.h"
#include "keyboard.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define Colormap XColormap
#define Cursor XCursor
#define Display XDisplay
#define Drawable XDrawable
#define Font XFont
#define GC XGC
#define Point XPoint
#define Rectangle XRectangle
#define Screen XScreen
#define Visual XVisual
#define Window XWindow
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/XShm.h>
#include "keysym2ucs.h"
#undef Colormap
#undef Cursor
#undef Display
#undef XDrawable
#undef Font
#undef GC
#undef Point
#undef Rectangle
#undef Screen
#undef Visual
#undef Window
#include <sys/ipc.h>
#include <sys/shm.h>
static int displaydepth;
extern ulong displaychan;
enum
{
DblTime = 300 /* double click time in msec */
};
/* screen data .... */
static uchar* gscreendata;
static uchar* xscreendata;
XColor map[256]; /* Inferno colormap array */
XColor mapr[256]; /* Inferno red colormap array */
XColor mapg[256]; /* Inferno green colormap array */
XColor mapb[256]; /* Inferno blue colormap array */
XColor map7[128]; /* Inferno colormap array */
uchar map7to8[128][2];
/* for copy/paste, lifted from plan9ports via drawterm */
static Atom clipboard;
static Atom utf8string;
static Atom targets;
static Atom text;
static Atom compoundtext;
static Atom cursorchange;
static XColormap xcmap; /* Default shared colormap */
static int infernotox11[256]; /* Values for mapping between */
static int infernortox11[256]; /* Values for mapping between */
static int infernogtox11[256]; /* Values for mapping between */
static int infernobtox11[256]; /* Values for mapping between */
static int triedscreen;
static XDrawable xdrawable;
static void xexpose(XEvent*);
static void xmouse(XEvent*);
static void xkeyboard(XEvent*);
static void xsetcursor(XEvent*);
static void xkbdproc(void*);
static void xdestroy(XEvent*);
static void xselect(XEvent*, XDisplay*);
static void xproc(void*);
static void xinitscreen(int, int, ulong, ulong*, int*);
static void initmap(XWindow, ulong, ulong*, int*);
static XGC creategc(XDrawable);
static void graphicsgmap(XColor*, int);
static void graphicscmap(XColor*);
static void graphicsrgbmap(XColor*, XColor*, XColor*);
static int xscreendepth;
static XDisplay* xdisplay; /* used holding draw lock */
static XDisplay* xmcon; /* used only in xproc */
static XDisplay* xkbdcon; /* used only in xkbdproc */
static XDisplay* xsnarfcon; /* used holding clip.lk */
static XVisual *xvis;
static XGC xgc;
static XImage *img;
static int is_shm;
static XShmSegmentInfo *shminfo;
static int realbuttons, fakebuttons;
static int lastx, lasty;
static int putsnarf, assertsnarf;
char *gkscanid = "emu_x11";
/*
* The documentation for the XSHM extension implies that if the server
* supports XSHM but is not the local machine, the XShm calls will
* return False; but this turns out not to be the case. Instead, the
* server throws a BadAccess error. So, we need to catch X errors
* around all of our XSHM calls, sigh.
*/
static int shm_got_x_error = False;
static XErrorHandler old_handler = 0;
static XErrorHandler old_io_handler = 0;
static int
shm_ehandler(XDisplay *dpy, XErrorEvent *error)
{
shm_got_x_error = 1;
return 0;
}
static void
clean_errhandlers(void)
{
/* remove X11 error handler(s) */
if(old_handler)
XSetErrorHandler(old_handler);
old_handler = 0;
if(old_io_handler)
XSetErrorHandler(old_io_handler);
old_io_handler = 0;
}
uchar*
attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
{
ulong c;
int depth;
Xsize &= ~0x3; /* ensure multiple of 4 */
r->min.x = 0;
r->min.y = 0;
r->max.x = Xsize;
r->max.y = Ysize;
c = displaychan;
if(c == 0)
c = CMAP8;
if(!triedscreen){
xinitscreen(Xsize, Ysize, c, chan, d);
/*
* moved xproc from here to end since it could cause an expose event and
* hence a flushmemscreen before xscreendata is initialized
*/
}
else{
*chan = displaychan;
*d = displaydepth;
}
*width = (Xsize/4)*(*d/8);
*softscreen = 1;
displaychan = *chan;
displaydepth = *d;
/* check for X Shared Memory Extension */
is_shm = XShmQueryExtension(xdisplay);
if(is_shm) {
shminfo = malloc(sizeof(XShmSegmentInfo));
if(shminfo == nil) {
fprint(2, "emu: cannot allocate XShmSegmentInfo\n");
cleanexit(0);
}
/* setup to catch X11 error(s) */
XSync(xdisplay, 0);
shm_got_x_error = 0;
if(old_handler != shm_ehandler)
old_handler = XSetErrorHandler(shm_ehandler);
if(old_io_handler != shm_ehandler)
old_io_handler = XSetErrorHandler(shm_ehandler);
img = XShmCreateImage(xdisplay, xvis, xscreendepth, ZPixmap,
NULL, shminfo, Xsize, Ysize);
XSync(xdisplay, 0);
/* did we get an X11 error? if so then try without shm */
if(shm_got_x_error) {
is_shm = 0;
free(shminfo);
shminfo = NULL;
clean_errhandlers();
goto next;
}
if(img == nil) {
fprint(2, "emu: can not allocate virtual screen buffer\n");
cleanexit(0);
}
shminfo->shmid = shmget(IPC_PRIVATE, img->bytes_per_line * img->height,
IPC_CREAT|0777);
shminfo->shmaddr = img->data = shmat(shminfo->shmid, 0, 0);
shminfo->readOnly = True;
if(!XShmAttach(xdisplay, shminfo)) {
fprint(2, "emu: cannot allocate virtual screen buffer\n");
cleanexit(0);
}
XSync(xdisplay, 0);
/*
* Delete the shared segment right now; the segment
* won't actually go away until both the client and
* server have deleted it. The server will delete it
* as soon as the client disconnects, so we might as
* well delete our side now as later.
*/
shmctl(shminfo->shmid, IPC_RMID, 0);
/* did we get an X11 error? if so then try without shm */
if(shm_got_x_error) {
is_shm = 0;
XDestroyImage(img);
XSync(xdisplay, 0);
free(shminfo);
shminfo = NULL;
clean_errhandlers();
goto next;
}
gscreendata = malloc(Xsize * Ysize * (displaydepth >> 3));
if(gscreendata == nil) {
fprint(2, "emu: cannot allocate screen buffer (%dx%d)\n", Xsize*Ysize);
cleanexit(0);
}
xscreendata = (uchar*)img->data;
clean_errhandlers();
}
next:
if(!is_shm) {
depth = xscreendepth;
if(depth == 24)
depth = 32;
/* allocate virtual screen */
gscreendata = malloc(Xsize * Ysize * (displaydepth >> 3));
xscreendata = malloc(Xsize * Ysize * (depth >> 3));
if(!gscreendata || !xscreendata) {
fprint(2, "emu: can not allocate virtual screen buffer\n");
return 0;
}
img = XCreateImage(xdisplay, xvis, xscreendepth, ZPixmap, 0,
(char*)xscreendata, Xsize, Ysize, 8, Xsize * (depth >> 3));
if(img == nil) {
fprint(2, "emu: can not allocate virtual screen buffer\n");
return 0;
}
}
if(!triedscreen){
triedscreen = 1;
kproc("xproc", xproc, xmcon, 0);
kproc("xkbdproc", xkbdproc, xkbdcon, KPX11); /* silly stack size for bloated X11 */
}
return gscreendata;
}
void
flushmemscreen(Rectangle r)
{
int x, y, width, height, dx;
uchar *p, *ep, *cp;
// Clip to screen
if(r.min.x < 0)
r.min.x = 0;
if(r.min.y < 0)
r.min.y = 0;
if(r.max.x >= Xsize)
r.max.x = Xsize - 1;
if(r.max.y >= Ysize)
r.max.y = Ysize - 1;
// is there anything left ...
width = r.max.x-r.min.x;
height = r.max.y-r.min.y;
if((width < 1) | (height < 1))
return;
// Blit the pixel data ...
if(displaydepth == 32){
u32int v, w, *dp, *wp, *edp;
dx = Xsize - width;
dp = (unsigned int *)(gscreendata + (r.min.y * Xsize + r.min.x) * 4);
wp = (unsigned int *)(xscreendata + (r.min.y * Xsize + r.min.x) * 4);
edp = (unsigned int *)(gscreendata + (r.max.y * Xsize + r.max.x) * 4);
while (dp < edp) {
const unsigned int *lp = dp + width;
while (dp < lp){
v = *dp++;
w = infernortox11[(v>>16)&0xff]<<16|infernogtox11[(v>>8)&0xff]<<8|infernobtox11[(v>>0)&0xff]<<0;
*wp++ = w;
}
dp += dx;
wp += dx;
}
}
else if(displaydepth == 8){
if(xscreendepth == 24 || xscreendepth == 32) {
u32int *wp;
dx = Xsize - width;
p = gscreendata + r.min.y * Xsize + r.min.x;
wp = (u32int *)(xscreendata + (r.min.y * Xsize + r.min.x) * 4);
ep = gscreendata + r.max.y * Xsize + r.max.x;
while (p < ep) {
const uchar *lp = p + width;
while (p < lp)
*wp++ = infernotox11[*p++];
p += dx;
wp += dx;
}
} else if(xscreendepth == 24) {
int v;
dx = Xsize - width;
p = gscreendata + r.min.y * Xsize + r.min.x;
cp = xscreendata + (r.min.y * Xsize + r.min.x) * 3;
ep = gscreendata + r.max.y * Xsize + r.max.x;
while (p < ep) {
const uchar *lp = p + width;
while (p < lp){
v = infernotox11[*p++];
cp[0] = (v>>16)&0xff;
cp[1] = (v>>8)&0xff;
cp[2] = (v>>0)&0xff;
cp += 3;
}
p += dx;
cp += 3*dx;
}
} else if(xscreendepth == 16) {
u16int *sp;
dx = Xsize - width;
p = gscreendata + r.min.y * Xsize + r.min.x;
sp = (unsigned short *)(xscreendata + (r.min.y * Xsize + r.min.x) * 2);
ep = gscreendata + r.max.y * Xsize + r.max.x;
while (p < ep) {
const uchar *lp = p + width;
while (p < lp)
*sp++ = infernotox11[*p++];
p += dx;
sp += dx;
}
} else if(xscreendepth == 8) {
dx = Xsize - width;
p = gscreendata + r.min.y * Xsize + r.min.x;
cp = xscreendata + r.min.y * Xsize + r.min.x;
ep = gscreendata + r.max.y * Xsize + r.max.x;
while (p < ep) {
const uchar *lp = p + width;
while (p < lp)
*cp++ = infernotox11[*p++];
p += dx;
cp += dx;
}
} else {
for (y = r.min.y; y < r.max.y; y++) {
x = r.min.x;
p = gscreendata + y * Xsize + x;
while (x < r.max.x)
XPutPixel(img, x++, y, infernotox11[*p++]);
}
}
}
else{
fprint(2, "emu: bad display depth %d\n", displaydepth);
cleanexit(0);
}
/* Display image on X11 */
if(is_shm)
XShmPutImage(xdisplay, xdrawable, xgc, img, r.min.x, r.min.y, r.min.x, r.min.y, width, height, 0);
else
XPutImage(xdisplay, xdrawable, xgc, img, r.min.x, r.min.y, r.min.x, r.min.y, width, height);
XSync(xdisplay, 0);
}
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
setpointer(int x, int y)
{
drawqlock();
XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, x, y);
XFlush(xdisplay);
drawqunlock();
}
static void
xkbdproc(void *arg)
{
XEvent event;
XDisplay *xd;
xd = arg;
/* BEWARE: the value of up is not defined for this proc on some systems */
XSelectInput(xd, xdrawable, KeyPressMask|KeyReleaseMask);
for(;;){
XNextEvent(xd, &event);
xkeyboard(&event);
xsetcursor(&event);
}
}
static void
xproc(void *arg)
{
ulong mask;
XEvent event;
XDisplay *xd;
closepgrp(up->env->pgrp);
closefgrp(up->env->fgrp);
closeegrp(up->env->egrp);
closesigs(up->env->sigs);
xd = arg;
mask = ButtonPressMask|
ButtonReleaseMask|
PointerMotionMask|
Button1MotionMask|
Button2MotionMask|
Button3MotionMask|
Button4MotionMask|
Button5MotionMask|
ExposureMask|
StructureNotifyMask;
XSelectInput(xd, xdrawable, mask);
for(;;){
XNextEvent(xd, &event);
xselect(&event, xd);
xmouse(&event);
xexpose(&event);
xdestroy(&event);
}
}
/*
* this crud is here because X11 can put huge amount of data
* on the stack during keyboard translation and cursor changing(!).
* we do both in a dedicated process with lots of stack, perhaps even enough.
*/
enum {
CursorSize= 32 /* biggest cursor size */
};
typedef struct ICursor ICursor;
struct ICursor {
ulong inuse;
int modify;
int hotx;
int hoty;
int w;
int h;
uchar src[(CursorSize/8)*CursorSize]; /* image and mask bitmaps */
uchar mask[(CursorSize/8)*CursorSize];
};
static ICursor icursor;
static void
xcurslock(void)
{
while(_tas(&icursor.inuse) != 0)
osyield();
}
static void
xcursunlock(void)
{
icursor.inuse = 0;
}
static void
xcursnotify(void)
{
XClientMessageEvent e;
memset(&e, 0, sizeof e);
e.type = ClientMessage;
e.window = xdrawable;
e.message_type = cursorchange;
e.format = 8;
XSendEvent(xkbdcon, xdrawable, True, KeyPressMask, (XEvent*)&e);
XFlush(xkbdcon);
}
void
drawcursor(Drawcursor* c)
{
uchar *bs, *bc, *ps, *pm;
int i, j, w, h, bpl;
if(c->data == nil){
drawqlock();
if(icursor.h != 0){
xcurslock();
icursor.h = 0;
icursor.modify = 1;
xcursunlock();
}
xcursnotify();
drawqunlock();
return;
}
drawqlock();
xcurslock();
icursor.modify = 0; /* xsetcursor will now ignore it */
xcursunlock();
h = (c->maxy-c->miny)/2; /* image, then mask */
bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1);
w = bpl;
if(w > CursorSize/8)
w = CursorSize/8;
ps = icursor.src;
pm = icursor.mask;
bc = c->data;
bs = c->data + h*bpl;
for(i = 0; i < h; i++){
for(j = 0; j < bpl && j < w; j++) {
*ps++ = revbyte(bs[j]);
*pm++ = revbyte(bs[j] | bc[j]);
}
bs += bpl;
bc += bpl;
}
icursor.h = h;
icursor.w = w*8;
icursor.hotx = c->hotx;
icursor.hoty = c->hoty;
icursor.modify = 1;
xcursnotify();
drawqunlock();
}
static void
xsetcursor(XEvent *e)
{
ICursor ic;
XCursor xc;
XColor fg, bg;
Pixmap xsrc, xmask;
static XCursor xcursor;
if(e->type != ClientMessage || !e->xclient.send_event || e->xclient.message_type != cursorchange)
return;
xcurslock();
if(icursor.modify == 0){
xcursunlock();
return;
}
icursor.modify = 0;
if(icursor.h == 0){
xcursunlock();
/* set the default system cursor */
if(xcursor != 0) {
XFreeCursor(xkbdcon, xcursor);
xcursor = 0;
}
XUndefineCursor(xkbdcon, xdrawable);
XFlush(xkbdcon);
return;
}
ic = icursor;
xcursunlock();
xsrc = XCreateBitmapFromData(xkbdcon, xdrawable, (char*)ic.src, ic.w, ic.h);
xmask = XCreateBitmapFromData(xkbdcon, xdrawable, (char*)ic.mask, ic.w, ic.h);
fg = map[0];
bg = map[255];
fg.pixel = infernotox11[0];
bg.pixel = infernotox11[255];
xc = XCreatePixmapCursor(xkbdcon, xsrc, xmask, &fg, &bg, -ic.hotx, -ic.hoty);
if(xc != 0) {
XDefineCursor(xkbdcon, xdrawable, xc);
if(xcursor != 0)
XFreeCursor(xkbdcon, xcursor);
xcursor = xc;
}
XFreePixmap(xkbdcon, xsrc);
XFreePixmap(xkbdcon, xmask);
XFlush(xkbdcon);
}
static void
xfullscreen(int set)
{
XClientMessageEvent e;
e.type = ClientMessage;
e.serial = 0;
e.send_event = True;
e.display = xdisplay;
e.window = xdrawable;
e.message_type = XInternAtom(xdisplay, "_NET_WM_STATE", False);
e.format = 32;
e.data.l[0] = set;
e.data.l[1] = XInternAtom(xdisplay, "_NET_WM_STATE_FULLSCREEN", False);
e.data.l[2] = None;
XSendEvent(xdisplay, DefaultRootWindow(xdisplay), False,
SubstructureRedirectMask | SubstructureNotifyMask,
(XEvent*)&e);
}
static void
xinitscreen(int xsize, int ysize, ulong c, ulong *chan, int *d)
{
char *argv[2];
char *dispname;
XWindow rootwin;
XWMHints hints;
XScreen *screen;
int rootscreennum;
XTextProperty name;
XClassHint classhints;
XSizeHints normalhints;
XSetWindowAttributes attrs;
xdrawable = 0;
dispname = getenv("DISPLAY");
if(dispname == nil)
dispname = "not set";
xdisplay = XOpenDisplay(NULL);
if(xdisplay == 0){
fprint(2, "emu: win-x11 open %r, DISPLAY is %s\n", dispname);
cleanexit(0);
}
rootscreennum = DefaultScreen(xdisplay);
rootwin = DefaultRootWindow(xdisplay);
xscreendepth = DefaultDepth(xdisplay, rootscreennum);
xvis = DefaultVisual(xdisplay, rootscreennum);
screen = DefaultScreenOfDisplay(xdisplay);
xcmap = DefaultColormapOfScreen(screen);
*chan = CMAP8;
*d = 8;
if(xvis->class != StaticColor) {
if(TYPE(c) == CGrey)
graphicsgmap(map, NBITS(c));
else{
graphicscmap(map);
graphicsrgbmap(mapr, mapg, mapb);
}
initmap(rootwin, c, chan, d);
}
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);
/*
* set up property as required by ICCCM
*/
name.value = (uchar*)"inferno";
name.encoding = XA_STRING;
name.format = 8;
name.nitems = strlen((char*)name.value);
normalhints.flags = USSize|PMaxSize;
normalhints.max_width = normalhints.width = xsize;
normalhints.max_height = normalhints.height = ysize;
hints.flags = InputHint|StateHint;
hints.input = 1;
hints.initial_state = NormalState;
classhints.res_name = "inferno";
classhints.res_class = "Inferno";
argv[0] = "inferno";
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 */
XMapWindow(xdisplay, xdrawable);
xfullscreen(1);
XFlush(xdisplay);
xgc = creategc(xdrawable);
xmcon = XOpenDisplay(NULL);
xsnarfcon = XOpenDisplay(NULL);
xkbdcon = XOpenDisplay(NULL);
if(xmcon == 0 || xsnarfcon == 0 || xkbdcon == 0){
fprint(2, "emu: win-x11 open %r, DISPLAY is %s\n", dispname);
cleanexit(0);
}
clipboard = XInternAtom(xmcon, "CLIPBOARD", False);
utf8string = XInternAtom(xmcon, "UTF8_STRING", False);
targets = XInternAtom(xmcon, "TARGETS", False);
text = XInternAtom(xmcon, "TEXT", False);
compoundtext = XInternAtom(xmcon, "COMPOUND_TEXT", False);
cursorchange = XInternAtom(xkbdcon, "TheCursorHasChanged", False);
}
static void
graphicsgmap(XColor *map, int d)
{
int i, j, s, m, p;
s = 8-d;
m = 1;
while(--d >= 0)
m *= 2;
m = 255/(m-1);
for(i=0; i < 256; i++){
j = (i>>s)*m;
p = 255-i;
map[p].red = map[p].green = map[p].blue = (255-j)*0x0101;
map[p].pixel = p;
map[p].flags = DoRed|DoGreen|DoBlue;
}
}
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);
/* was idx = 255 - idx; */
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;
}
}
}
}
}
static void
graphicsrgbmap(XColor *mapr, XColor *mapg, XColor *mapb)
{
int i;
memset(mapr, 0, 256*sizeof(XColor));
memset(mapg, 0, 256*sizeof(XColor));
memset(mapb, 0, 256*sizeof(XColor));
for(i=0; i < 256; i++){
mapr[i].red = mapg[i].green = mapb[i].blue = i*0x0101;
mapr[i].pixel = mapg[i].pixel = mapb[i].pixel = i;
mapr[i].flags = mapg[i].flags = mapb[i].flags = DoRed|DoGreen|DoBlue;
}
}
/*
* Initialize and install the Inferno colormap as a private colormap for this
* application. Inferno gets the best colors here when it has the cursor focus.
*/
static void
initmap(XWindow w, ulong cc, ulong *chan, int *d)
{
XColor c;
int i;
if(xscreendepth <= 1)
return;
if(xvis->class == TrueColor || xvis->class == DirectColor) {
for(i = 0; i < 256; i++) {
c = map[i];
/* find out index into colormap for our RGB */
if(!XAllocColor(xdisplay, xcmap, &c)) {
fprint(2, "emu: win-x11 can't alloc color\n");
cleanexit(0);
}
infernotox11[map[i].pixel] = c.pixel;
if(xscreendepth >= 24){
c = mapr[i];
XAllocColor(xdisplay, xcmap, &c);
infernortox11[i] = (c.pixel>>16)&0xff;
c = mapg[i];
XAllocColor(xdisplay, xcmap, &c);
infernogtox11[i] = (c.pixel>>8)&0xff;
c = mapb[i];
XAllocColor(xdisplay, xcmap, &c);
infernobtox11[i] = (c.pixel>>0)&0xff;
}
}
if(TYPE(cc) != CGrey && cc != CMAP8 && xscreendepth >= 24){
*chan = XRGB32;
*d = 32;
}
}
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++)
infernotox11[i] = i;
} else {
for(i = 0; i < 128; i++) {
c = map7[i];
if(!XAllocColor(xdisplay, xcmap, &c)) {
fprint(2, "emu: win-x11 can't alloc colors in default map, don't use -7\n");
cleanexit(0);
}
infernotox11[map7to8[i][0]] = c.pixel;
infernotox11[map7to8[i][1]] = c.pixel;
}
}
}
else {
xtblbit = 0;
fprint(2, "emu: win-x11 unsupported visual class %d\n", xvis->class);
}
return;
}
static void
xdestroy(XEvent *e)
{
XDestroyWindowEvent *xe;
if(e->type != DestroyNotify)
return;
xe = (XDestroyWindowEvent*)e;
if(xe->window == xdrawable)
cleanexit(0);
}
/*
* Disable generation of GraphicsExpose/NoExpose events in the XGC.
*/
static XGC
creategc(XDrawable 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;
drawqlock();
flushmemscreen(r);
drawqunlock();
}
static void
xkeyboard(XEvent *e)
{
int ind, md;
KeySym k;
if(e->type == KeyPress && gkscanq != nil){
uchar ch = e->xkey.keycode;
if(e->xany.type == KeyRelease)
ch |= 0x80;
qproduce(gkscanq, &ch, 1);
return;
}
if(e->type == KeyPress || e->type == KeyRelease) {
int c = e->xkey.keycode;
if (c >= 72 && c <= 74) {
int b = c == 74? Button1Mask: c == 72? Button2Mask : Button3Mask;
if (e->type == KeyPress)
fakebuttons |= b;
else
fakebuttons &= ~b;
if (realbuttons&Button1Mask) {
c = 0;
if (fakebuttons&Button1Mask)
c |= 1;
if (fakebuttons&Button2Mask)
c |= 2;
if (fakebuttons&Button3Mask)
c |= 4;
mousetrack(c, lastx, lasty, 0);
}
return;
}
}
/*
* 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;
md = e->xkey.state;
ind = 0;
if(md & ShiftMask)
ind = 1;
if(0){
k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, ind);
/* May have to try unshifted version */
if(k == NoSymbol && ind == 1)
k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, 0);
}else
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 = Left;
break;
case XK_Up:
case XK_KP_Up:
k = Up;
break;
case XK_Down:
case XK_KP_Down:
k = Down;
break;
case XK_Right:
case XK_KP_Right:
k = Right;
break;
// case XK_Page_Down:
// case XK_KP_Page_Down:
// k = Kpgdown;
// break;
case XK_End:
case XK_KP_End:
k = End;
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 = Latin;
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(md & ControlMask)
k &= 0x9f;
if(0){
if(k == '\t' && ind)
k = BackTab;
if(md & Mod1Mask)
k = APP|(k&0xff);
}
if(k == NoSymbol)
return;
gkbdputc(gkbdq, k);
}
static void
xmouse(XEvent *e)
{
int s, dbl;
XButtonEvent *be;
XMotionEvent *me;
XEvent motion;
int x, y, b;
static ulong lastb, lastt;
if(putsnarf != assertsnarf){
assertsnarf = putsnarf;
XSetSelectionOwner(xmcon, XA_PRIMARY, xdrawable, CurrentTime);
if(clipboard != None)
XSetSelectionOwner(xmcon, clipboard, xdrawable, CurrentTime);
XFlush(xmcon);
}
dbl = 0;
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;
x = be->x;
y = be->y;
realbuttons = s = be->state;
if(be->button == lastb && be->time - lastt < DblTime)
dbl = 1;
lastb = be->button;
lastt = be->time;
if (fakebuttons)
s = fakebuttons;
else
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;
x = be->x;
y = be->y;
realbuttons = s = be->state;
if (fakebuttons)
s = 0;
else
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;
/* remove excess MotionNotify events from queue and keep last one */
while(XCheckTypedWindowEvent(xmcon, xdrawable, MotionNotify, &motion) == True)
me = (XMotionEvent *) &motion;
realbuttons = s = me->state;
if (fakebuttons)
s = fakebuttons;
x = me->x;
y = me->y;
break;
default:
return;
}
b = 0;
if(s & Button1Mask)
b |= 1;
if(s & Button2Mask)
b |= 2;
if(s & Button3Mask)
b |= 4;
if(s & Button4Mask)
b |= 8;
if(s & Button5Mask)
b |= 16;
if(dbl)
b |= 1<<8;
lastx = x;
lasty = y;
mousetrack(b, x, y, 0);
}
#include "x11-keysym2ucs.c"
/*
* Cut and paste. Just couldn't stand to make this simple...
*/
enum{
SnarfSize= 100*1024
};
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 len, lastlen, dummy;
int fmt, i;
XWindow 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++){
osmillisleep(100);
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, xe->target,
8, PropModeReplace, (uchar*)a, sizeof a);
}else if(xe->target == XA_STRING || xe->target == utf8string || xe->target == text || xe->target == compoundtext){
/* 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{
iprint("get %d\n", xe->target);
name = XGetAtomName(xd, xe->target);
if(name == nil)
iprint("XGetAtomName failed\n");
else if(strcmp(name, "TIMESTAMP") != 0)
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;
}
|