Files
plan9port/src/cmd/devdraw/x11-screen.c
Roger Peppe 9da5b44513 cmd/devdraw: avoid use of X button state field
This bug is an intermittent annoyance and does not seem likely to be
fixed soon. It means that every so often my acme becomes unusable
because it behaves as if a mouse button is permanently set.

Avoid that issue by keeping track of mouse button state directly.

While we're about it, fix another annoying issue: under Linux the
FocusOut event reset the kstate field but not the kbuttons field,
meaning that the buttons state could inappropriately persist. This
happened occasionally by accident, but was easy to reproduce: with the
mouse button held down, drag outside the window and click Alt-Tab to
switch away from the window. We see a KeyDown event followed by FocusOut
but no KeyUp event.

Change-Id: I3708316478c80c41806d7157ae9929c6cd84b662
2025-03-29 11:23:26 -04:00

1776 lines
38 KiB
C

#include <u.h>
#include "x11-inc.h"
#include "x11-keysym2ucs.h"
#include <errno.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <memlayer.h>
#include <keyboard.h>
#include <mouse.h>
#include <cursor.h>
#include <thread.h>
#include "x11-memdraw.h"
#include "devdraw.h"
#undef time
static void plan9cmap(void);
static int setupcmap(XWindow);
static XGC xgc(XDrawable, int, int);
#define Mask MouseMask|ExposureMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask|EnterWindowMask|LeaveWindowMask|FocusChangeMask
#define MouseMask (\
ButtonPressMask|\
ButtonReleaseMask|\
PointerMotionMask|\
Button1MotionMask|\
Button2MotionMask|\
Button3MotionMask)
Xprivate _x;
static void runxevent(XEvent *xev);
static int _xconfigure(Xwin *w, XEvent *e);
static int _xdestroy(Xwin *w, XEvent *e);
static void _xexpose(Xwin *w, XEvent *e);
static int _xreplacescreenimage(Client *client);
static int _xtoplan9mouse(Xwin *w, XEvent *e, Mouse *m);
static void _xmovewindow(Xwin *w, Rectangle r);
static int _xtoplan9kbd(XEvent *e);
static int _xselect(XEvent *e);
static void rpc_resizeimg(Client*);
static void rpc_resizewindow(Client*, Rectangle);
static void rpc_setcursor(Client*, Cursor*, Cursor2*);
static void rpc_setlabel(Client*, char*);
static void rpc_setmouse(Client*, Point);
static void rpc_topwin(Client*);
static void rpc_bouncemouse(Client*, Mouse);
static void rpc_flush(Client*, Rectangle);
static ClientImpl x11impl = {
rpc_resizeimg,
rpc_resizewindow,
rpc_setcursor,
rpc_setlabel,
rpc_setmouse,
rpc_topwin,
rpc_bouncemouse,
rpc_flush
};
static Xwin*
newxwin(Client *c)
{
Xwin *w;
w = mallocz(sizeof *w, 1);
if(w == nil)
sysfatal("out of memory");
w->client = c;
w->next = _x.windows;
_x.windows = w;
c->impl = &x11impl;
c->view = w;
return w;
}
static Xwin*
findxwin(XDrawable d)
{
Xwin *w, **l;
for(l=&_x.windows; (w=*l) != nil; l=&w->next) {
if(w->drawable == d) {
/* move to front */
*l = w->next;
w->next = _x.windows;
_x.windows = w;
return w;
}
}
return nil;
}
static int
xerror(XDisplay *d, XErrorEvent *e)
{
char buf[200];
if(e->request_code == 42) /* XSetInputFocus */
return 0;
if(e->request_code == 18) /* XChangeProperty */
return 0;
/*
* BadDrawable happens in apps that get resized a LOT,
* e.g. when KDE is configured to resize continuously
* during a window drag.
*/
if(e->error_code == 9) /* BadDrawable */
return 0;
fprint(2, "X error: error_code=%d, request_code=%d, minor=%d disp=%p\n",
e->error_code, e->request_code, e->minor_code, d);
XGetErrorText(d, e->error_code, buf, sizeof buf);
fprint(2, "%s\n", buf);
return 0;
}
static int
xioerror(XDisplay *d)
{
/*print("X I/O error\n"); */
exit(0);
/*sysfatal("X I/O error\n");*/
abort();
return -1;
}
static void xloop(void);
static QLock xlk;
void
xlock(void)
{
qlock(&xlk);
}
void
xunlock(void)
{
qunlock(&xlk);
}
void
gfx_main(void)
{
char *disp;
int i, n, xrootid;
XPixmapFormatValues *pfmt;
XScreen *xscreen;
XVisualInfo xvi;
XWindow xrootwin;
/*
if(XInitThreads() == 0)
sysfatal("XInitThread: %r");
*/
/*
* Connect to X server.
*/
_x.display = XOpenDisplay(NULL);
if(_x.display == nil){
disp = getenv("DISPLAY");
werrstr("XOpenDisplay %s: %r", disp ? disp : ":0");
free(disp);
sysfatal("%r");
}
_x.fd = ConnectionNumber(_x.display);
XSetErrorHandler(xerror);
XSetIOErrorHandler(xioerror);
xrootid = DefaultScreen(_x.display);
xrootwin = DefaultRootWindow(_x.display);
/*
* Figure out underlying screen format.
*/
if(XMatchVisualInfo(_x.display, xrootid, 24, TrueColor, &xvi)
|| XMatchVisualInfo(_x.display, xrootid, 24, DirectColor, &xvi)){
_x.vis = xvi.visual;
_x.depth = 24;
}
else
if(XMatchVisualInfo(_x.display, xrootid, 16, TrueColor, &xvi)
|| XMatchVisualInfo(_x.display, xrootid, 16, DirectColor, &xvi)){
_x.vis = xvi.visual;
_x.depth = 16;
}
else
if(XMatchVisualInfo(_x.display, xrootid, 15, TrueColor, &xvi)
|| XMatchVisualInfo(_x.display, xrootid, 15, DirectColor, &xvi)){
_x.vis = xvi.visual;
_x.depth = 15;
}
else
if(XMatchVisualInfo(_x.display, xrootid, 8, PseudoColor, &xvi)
|| XMatchVisualInfo(_x.display, xrootid, 8, StaticColor, &xvi)){
if(_x.depth > 8){
werrstr("can't deal with colormapped depth %d screens",
_x.depth);
goto err0;
}
_x.vis = xvi.visual;
_x.depth = 8;
}
else{
_x.depth = DefaultDepth(_x.display, xrootid);
if(_x.depth != 8){
werrstr("can't understand depth %d screen", _x.depth);
goto err0;
}
_x.vis = DefaultVisual(_x.display, xrootid);
}
if(DefaultDepth(_x.display, xrootid) == _x.depth)
_x.usetable = 1;
/*
* _x.depth is only the number of significant pixel bits,
* not the total number of pixel bits. We need to walk the
* display list to find how many actual bits are used
* per pixel.
*/
_x.chan = 0;
pfmt = XListPixmapFormats(_x.display, &n);
for(i=0; i<n; i++){
if(pfmt[i].depth == _x.depth){
switch(pfmt[i].bits_per_pixel){
case 1: /* untested */
_x.chan = GREY1;
break;
case 2: /* untested */
_x.chan = GREY2;
break;
case 4: /* untested */
_x.chan = GREY4;
break;
case 8:
_x.chan = CMAP8;
break;
case 15:
_x.chan = RGB15;
break;
case 16: /* how to tell RGB15? */
_x.chan = RGB16;
break;
case 24: /* untested (impossible?) */
_x.chan = RGB24;
break;
case 32:
_x.chan = XRGB32;
break;
}
}
}
XFree(pfmt);
if(_x.chan == 0){
werrstr("could not determine screen pixel format");
goto err0;
}
/*
* Set up color map if necessary.
*/
xscreen = DefaultScreenOfDisplay(_x.display);
_x.cmap = DefaultColormapOfScreen(xscreen);
if(_x.vis->class != StaticColor){
plan9cmap();
setupcmap(xrootwin);
}
gfx_started();
xloop();
err0:
XCloseDisplay(_x.display);
sysfatal("%r");
}
static void
xloop(void)
{
fd_set rd, wr, xx;
XEvent event;
xlock();
_x.fd = ConnectionNumber(_x.display);
for(;;) {
FD_ZERO(&rd);
FD_ZERO(&wr);
FD_ZERO(&xx);
FD_SET(_x.fd, &rd);
FD_SET(_x.fd, &xx);
if(_x.windows != nil)
XSelectInput(_x.display, _x.windows->drawable, Mask); // TODO: when is this needed?
XFlush(_x.display);
xunlock();
again:
if(select(_x.fd+1, &rd, &wr, &xx, nil) < 0) {
if(errno == EINTR)
goto again;
sysfatal("select: %r"); // TODO: quiet exit?
}
xlock();
while(XPending(_x.display)) {
XNextEvent(_x.display, &event);
runxevent(&event);
}
}
}
static int kcodecontrol, kcodealt, kcodeshift;
/*
* Handle an incoming X event.
*/
static void
runxevent(XEvent *xev)
{
int c;
int modp;
KeySym k;
static Mouse m;
XButtonEvent *be;
XKeyEvent *ke;
Xwin *w;
modp = 0;
#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
w = nil;
switch(xev->type){
case Expose:
w = findxwin(((XExposeEvent*)xev)->window);
break;
case DestroyNotify:
w = findxwin(((XDestroyWindowEvent*)xev)->window);
break;
case ConfigureNotify:
w = findxwin(((XConfigureEvent*)xev)->window);
break;
case ButtonPress:
case ButtonRelease:
w = findxwin(((XButtonEvent*)xev)->window);
break;
case MotionNotify:
w = findxwin(((XMotionEvent*)xev)->window);
break;
case KeyRelease:
case KeyPress:
w = findxwin(((XKeyEvent*)xev)->window);
break;
case FocusOut:
w = findxwin(((XFocusChangeEvent*)xev)->window);
break;
}
if(w == nil)
w = _x.windows;
int shift;
switch(xev->type){
case Expose:
_xexpose(w, xev);
break;
case DestroyNotify:
if(_xdestroy(w, xev))
threadexitsall(nil);
break;
case ConfigureNotify:
if(_xconfigure(w, xev))
_xreplacescreenimage(w->client);
break;
case ButtonPress:
be = (XButtonEvent*)xev;
if(be->button == 1) {
if(_x.kstate & ControlMask)
be->button = 2;
else if(_x.kstate & Mod1Mask)
be->button = 3;
}
// fall through
case ButtonRelease:
_x.altdown = 0;
// fall through
case MotionNotify:
if(_xtoplan9mouse(w, xev, &m) < 0)
return;
shift = 0;
if(_x.kstate & ShiftMask)
shift = 5;
gfx_mousetrack(w->client, m.xy.x, m.xy.y, (m.buttons|_x.kbuttons)<<shift, m.msec);
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)
_x.altdown = 1;
else if(_x.altdown) {
_x.altdown = 0;
gfx_keystroke(w->client, Kalt);
}
break;
}
if(xev->type == KeyPress)
switch(k) {
case XK_Control_L:
case XK_Control_R:
kcodecontrol = ke->keycode;
c |= ControlMask;
modp = 1;
break;
case XK_Alt_L:
case XK_Alt_R:
kcodealt = ke->keycode;
c |= Mod1Mask;
modp = 1;
break;
case XK_Shift_L:
case XK_Shift_R:
kcodeshift = ke->keycode;
c |= ShiftMask;
modp = 1;
break;
}
else {
if(ke->keycode == kcodecontrol){
c &= ~ControlMask;
modp = 1;
} else if(ke->keycode == kcodealt){
c &= ~Mod1Mask;
modp = 1;
} else if(ke->keycode == kcodeshift) {
c &= ~ShiftMask;
modp = 1;
}
}
if(modp){
_x.kstate = c;
if(m.buttons || _x.kbuttons) {
int shift = 0;
_x.altdown = 0; // used alt
_x.kbuttons = 0;
if(c & ControlMask)
_x.kbuttons |= 2;
if(c & Mod1Mask)
_x.kbuttons |= 4;
if(c & ShiftMask)
shift = 5;
gfx_mousetrack(w->client, m.xy.x, m.xy.y, (m.buttons|_x.kbuttons)<<shift, m.msec);
}
modp = 0;
}
if(xev->type != KeyPress)
break;
if(k == XK_F11){
w->fullscreen = !w->fullscreen;
_xmovewindow(w, w->fullscreen ? w->screenrect : w->windowrect);
return;
}
if((c = _xtoplan9kbd(xev)) < 0)
return;
gfx_keystroke(w->client, c);
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.
*/
_x.kstate = 0;
_x.kbuttons = 0;
_x.altdown = 0;
gfx_abortcompose(w->client);
break;
case SelectionRequest:
_xselect(xev);
break;
}
}
static Memimage*
xattach(Client *client, char *label, char *winsize)
{
char *argv[2];
int havemin, height, mask, width, x, y;
Rectangle r;
XClassHint classhint;
XDrawable pmid;
XScreen *xscreen;
XSetWindowAttributes attr;
XSizeHints normalhint;
XTextProperty name;
XWindow xrootwin;
XWindowAttributes wattr;
XWMHints hint;
Atom atoms[2];
Xwin *w;
USED(client);
xscreen = DefaultScreenOfDisplay(_x.display);
xrootwin = DefaultRootWindow(_x.display);
/*
* We get to choose the initial rectangle size.
* This is arbitrary. In theory we should read the
* command line and allow the traditional X options.
*/
mask = 0;
x = 0;
y = 0;
if(winsize && winsize[0]){
if(parsewinsize(winsize, &r, &havemin) < 0)
sysfatal("%r");
}else{
/*
* Parse the various X resources. Thanks to Peter Canning.
*/
char *screen_resources, *display_resources, *geom,
*geomrestype, *home, *file, *dpitype;
XrmDatabase database;
XrmValue geomres, dpires;
database = XrmGetDatabase(_x.display);
screen_resources = XScreenResourceString(xscreen);
if(screen_resources != nil){
XrmCombineDatabase(XrmGetStringDatabase(screen_resources), &database, False);
XFree(screen_resources);
}
display_resources = XResourceManagerString(_x.display);
if(display_resources == nil){
home = getenv("HOME");
if(home!=nil && (file=smprint("%s/.Xdefaults", home)) != nil){
XrmCombineFileDatabase(file, &database, False);
free(file);
}
free(home);
}else
XrmCombineDatabase(XrmGetStringDatabase(display_resources), &database, False);
if (XrmGetResource(database, "Xft.dpi", "String", &dpitype, &dpires) == True) {
if (dpires.addr) {
client->displaydpi = atoi(dpires.addr);
}
}
geom = smprint("%s.geometry", label);
if(geom && XrmGetResource(database, geom, nil, &geomrestype, &geomres))
mask = XParseGeometry(geomres.addr, &x, &y, (uint*)&width, (uint*)&height);
XrmDestroyDatabase(database);
free(geom);
if((mask & WidthValue) && (mask & HeightValue)){
r = Rect(0, 0, width, height);
}else{
r = Rect(0, 0, WidthOfScreen(xscreen)*3/4,
HeightOfScreen(xscreen)*3/4);
if(Dx(r) > Dy(r)*3/2)
r.max.x = r.min.x + Dy(r)*3/2;
if(Dy(r) > Dx(r)*3/2)
r.max.y = r.min.y + Dx(r)*3/2;
}
if(mask & XNegative){
x += WidthOfScreen(xscreen);
}
if(mask & YNegative){
y += HeightOfScreen(xscreen);
}
havemin = 0;
}
w = newxwin(client);
memset(&attr, 0, sizeof attr);
attr.colormap = _x.cmap;
attr.background_pixel = ~0;
attr.border_pixel = 0;
w->drawable = XCreateWindow(
_x.display, /* display */
xrootwin, /* parent */
x, /* x */
y, /* y */
Dx(r), /* width */
Dy(r), /* height */
0, /* border width */
_x.depth, /* depth */
InputOutput, /* class */
_x.vis, /* visual */
/* valuemask */
CWBackPixel|CWBorderPixel|CWColormap,
&attr /* attributes (the above aren't?!) */
);
/*
* Label and other properties required by ICCCCM.
*/
memset(&name, 0, sizeof name);
if(label == nil)
label = "pjw-face-here";
name.value = (uchar*)label;
name.encoding = XA_STRING;
name.format = 8;
name.nitems = strlen((char*)name.value);
memset(&normalhint, 0, sizeof normalhint);
normalhint.flags = PSize|PMaxSize;
if(winsize && winsize[0]){
normalhint.flags &= ~PSize;
normalhint.flags |= USSize;
normalhint.width = Dx(r);
normalhint.height = Dy(r);
}else{
if((mask & WidthValue) && (mask & HeightValue)){
normalhint.flags &= ~PSize;
normalhint.flags |= USSize;
normalhint.width = width;
normalhint.height = height;
}
if((mask & WidthValue) && (mask & HeightValue)){
normalhint.flags |= USPosition;
normalhint.x = x;
normalhint.y = y;
}
}
normalhint.max_width = WidthOfScreen(xscreen);
normalhint.max_height = HeightOfScreen(xscreen);
memset(&hint, 0, sizeof hint);
hint.flags = InputHint|StateHint;
hint.input = 1;
hint.initial_state = NormalState;
memset(&classhint, 0, sizeof classhint);
classhint.res_name = label;
classhint.res_class = label;
argv[0] = label;
argv[1] = nil;
XSetWMProperties(
_x.display, /* display */
w->drawable, /* window */
&name, /* XA_WM_NAME property */
&name, /* XA_WM_ICON_NAME property */
argv, /* XA_WM_COMMAND */
1, /* argc */
&normalhint, /* XA_WM_NORMAL_HINTS */
&hint, /* XA_WM_HINTS */
&classhint /* XA_WM_CLASSHINTS */
);
XFlush(_x.display);
if(havemin){
XWindowChanges ch;
memset(&ch, 0, sizeof ch);
ch.x = r.min.x;
ch.y = r.min.y;
XConfigureWindow(_x.display, w->drawable, CWX|CWY, &ch);
/*
* Must pretend origin is 0,0 for X.
*/
r = Rect(0,0,Dx(r),Dy(r));
}
/*
* Look up clipboard atom.
*/
if(_x.clipboard == 0) {
_x.clipboard = XInternAtom(_x.display, "CLIPBOARD", False);
_x.utf8string = XInternAtom(_x.display, "UTF8_STRING", False);
_x.targets = XInternAtom(_x.display, "TARGETS", False);
_x.text = XInternAtom(_x.display, "TEXT", False);
_x.compoundtext = XInternAtom(_x.display, "COMPOUND_TEXT", False);
_x.takefocus = XInternAtom(_x.display, "WM_TAKE_FOCUS", False);
_x.losefocus = XInternAtom(_x.display, "_9WM_LOSE_FOCUS", False);
_x.wmprotos = XInternAtom(_x.display, "WM_PROTOCOLS", False);
}
atoms[0] = _x.takefocus;
atoms[1] = _x.losefocus;
XChangeProperty(_x.display, w->drawable, _x.wmprotos, XA_ATOM, 32,
PropModeReplace, (uchar*)atoms, 2);
/*
* Put the window on the screen, check to see what size we actually got.
*/
XMapWindow(_x.display, w->drawable);
XSync(_x.display, False);
if(!XGetWindowAttributes(_x.display, w->drawable, &wattr))
fprint(2, "XGetWindowAttributes failed\n");
else if(wattr.width && wattr.height){
if(wattr.width != Dx(r) || wattr.height != Dy(r)){
r.max.x = wattr.width;
r.max.y = wattr.height;
}
}else
fprint(2, "XGetWindowAttributes: bad attrs\n");
w->screenrect = Rect(0, 0, WidthOfScreen(xscreen), HeightOfScreen(xscreen));
w->windowrect = r;
/*
* Allocate our local backing store.
*/
w->screenr = r;
w->screenpm = XCreatePixmap(_x.display, w->drawable, Dx(r), Dy(r), _x.depth);
w->nextscreenpm = w->screenpm;
w->screenimage = _xallocmemimage(r, _x.chan, w->screenpm);
client->mouserect = r;
/*
* Allocate some useful graphics contexts for the future.
* These can be used with any drawable matching w->drawable's
* pixel format (which is all the drawables we create).
*/
if(_x.gcfill == 0) {
_x.gcfill = xgc(w->screenpm, FillSolid, -1);
_x.gccopy = xgc(w->screenpm, -1, -1);
_x.gcsimplesrc = xgc(w->screenpm, FillStippled, -1);
_x.gczero = xgc(w->screenpm, -1, -1);
_x.gcreplsrc = xgc(w->screenpm, FillTiled, -1);
pmid = XCreatePixmap(_x.display, w->drawable, 1, 1, 1);
_x.gcfill0 = xgc(pmid, FillSolid, 0);
_x.gccopy0 = xgc(pmid, -1, -1);
_x.gcsimplesrc0 = xgc(pmid, FillStippled, -1);
_x.gczero0 = xgc(pmid, -1, -1);
_x.gcreplsrc0 = xgc(pmid, FillTiled, -1);
XFreePixmap(_x.display, pmid);
}
return w->screenimage;
}
Memimage*
rpc_attach(Client *client, char *label, char *winsize)
{
Memimage *m;
xlock();
m = xattach(client, label, winsize);
xunlock();
return m;
}
void
rpc_setlabel(Client *client, char *label)
{
Xwin *w = (Xwin*)client->view;
XTextProperty name;
/*
* Label and other properties required by ICCCCM.
*/
xlock();
memset(&name, 0, sizeof name);
if(label == nil)
label = "pjw-face-here";
name.value = (uchar*)label;
name.encoding = XA_STRING;
name.format = 8;
name.nitems = strlen((char*)name.value);
XSetWMProperties(
_x.display, /* display */
w->drawable, /* window */
&name, /* XA_WM_NAME property */
&name, /* XA_WM_ICON_NAME property */
nil, /* XA_WM_COMMAND */
0, /* argc */
nil, /* XA_WM_NORMAL_HINTS */
nil, /* XA_WM_HINTS */
nil /* XA_WM_CLASSHINTS */
);
XFlush(_x.display);
xunlock();
}
/*
* Create a GC with a particular fill style and XXX.
* Disable generation of GraphicsExpose/NoExpose events in the GC.
*/
static XGC
xgc(XDrawable d, int fillstyle, int foreground)
{
XGC gc;
XGCValues v;
memset(&v, 0, sizeof v);
v.function = GXcopy;
v.graphics_exposures = False;
gc = XCreateGC(_x.display, d, GCFunction|GCGraphicsExposures, &v);
if(fillstyle != -1)
XSetFillStyle(_x.display, gc, fillstyle);
if(foreground != -1)
XSetForeground(_x.display, gc, 0);
return gc;
}
/*
* Initialize map with the Plan 9 rgbv color map.
*/
static void
plan9cmap(void)
{
int r, g, b, cr, cg, cb, v, num, den, idx, v7, idx7;
static int once;
if(once)
return;
once = 1;
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);
_x.map[idx].red = cr*0x0101;
_x.map[idx].green = cg*0x0101;
_x.map[idx].blue = cb*0x0101;
_x.map[idx].pixel = idx;
_x.map[idx].flags = DoRed|DoGreen|DoBlue;
v7 = v >> 1;
idx7 = r*32 + v7*16 + g*4 + b;
if((v & 1) == v7){
_x.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;
}
_x.map7[idx7].red = cr*0x0101;
_x.map7[idx7].green = cg*0x0101;
_x.map7[idx7].blue = cb*0x0101;
_x.map7[idx7].pixel = idx7;
_x.map7[idx7].flags = DoRed|DoGreen|DoBlue;
}
else
_x.map7to8[idx7][1] = idx;
}
}
/*
* Initialize and install the rgbv color map as a private color map
* for this application. It gets the best colors when it has the
* cursor focus.
*
* We always choose the best depth possible, but that might not
* be the default depth. On such "suboptimal" systems, we have to allocate an
* empty color map anyway, according to Axel Belinfante.
*/
static int
setupcmap(XWindow w)
{
char buf[30];
int i;
u32int p, pp;
XColor c;
if(_x.depth <= 1)
return 0;
if(_x.depth >= 24) {
if(_x.usetable == 0)
_x.cmap = XCreateColormap(_x.display, w, _x.vis, AllocNone);
/*
* 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 _x.vis->byte_order but
* some displays say MSB even though they run on LSB.
* Besides, this is more anal.
*/
c = _x.map[19]; /* known to have different R, G, B values */
if(!XAllocColor(_x.display, _x.cmap, &c)){
werrstr("XAllocColor: %r");
return -1;
}
p = c.pixel;
pp = rgb2cmap((p>>16)&0xff,(p>>8)&0xff,p&0xff);
if(pp != _x.map[19].pixel) {
/* check if endian is other way */
pp = rgb2cmap(p&0xff,(p>>8)&0xff,(p>>16)&0xff);
if(pp != _x.map[19].pixel){
werrstr("cannot detect X server byte order");
return -1;
}
switch(_x.chan){
case RGB24:
_x.chan = BGR24;
break;
case XRGB32:
_x.chan = XBGR32;
break;
default:
werrstr("cannot byteswap channel %s",
chantostr(buf, _x.chan));
break;
}
}
}else if(_x.vis->class == TrueColor || _x.vis->class == DirectColor){
/*
* Do nothing. We have no way to express a
* mixed-endian 16-bit screen, so pretend they don't exist.
*/
if(_x.usetable == 0)
_x.cmap = XCreateColormap(_x.display, w, _x.vis, AllocNone);
}else if(_x.vis->class == PseudoColor){
if(_x.usetable == 0){
_x.cmap = XCreateColormap(_x.display, w, _x.vis, AllocAll);
XStoreColors(_x.display, _x.cmap, _x.map, 256);
for(i = 0; i < 256; i++){
_x.tox11[i] = i;
_x.toplan9[i] = i;
}
}else{
for(i = 0; i < 128; i++){
c = _x.map7[i];
if(!XAllocColor(_x.display, _x.cmap, &c)){
werrstr("can't allocate colors in 7-bit map");
return -1;
}
_x.tox11[_x.map7to8[i][0]] = c.pixel;
_x.tox11[_x.map7to8[i][1]] = c.pixel;
_x.toplan9[c.pixel] = _x.map7to8[i][0];
}
}
}else{
werrstr("unsupported visual class %d", _x.vis->class);
return -1;
}
return 0;
}
void
rpc_shutdown(void)
{
}
void
rpc_flush(Client *client, Rectangle r)
{
Xwin *w = (Xwin*)client->view;
xlock();
if(w->nextscreenpm != w->screenpm){
XSync(_x.display, False);
XFreePixmap(_x.display, w->screenpm);
w->screenpm = w->nextscreenpm;
}
if(r.min.x >= r.max.x || r.min.y >= r.max.y) {
xunlock();
return;
}
XCopyArea(_x.display, w->screenpm, w->drawable, _x.gccopy, r.min.x, r.min.y,
Dx(r), Dy(r), r.min.x, r.min.y);
XFlush(_x.display);
xunlock();
}
static void
_xexpose(Xwin *w, XEvent *e)
{
XExposeEvent *xe;
Rectangle r;
if(w->screenpm != w->nextscreenpm)
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;
XCopyArea(_x.display, w->screenpm, w->drawable, _x.gccopy, r.min.x, r.min.y,
Dx(r), Dy(r), r.min.x, r.min.y);
XSync(_x.display, False);
}
static int
_xdestroy(Xwin *w, XEvent *e)
{
XDestroyWindowEvent *xe;
xe = (XDestroyWindowEvent*)e;
if(xe->window == w->drawable){
w->destroyed = 1;
return 1;
}
return 0;
}
static int
_xconfigure(Xwin *w, XEvent *e)
{
Rectangle r;
XConfigureEvent *xe = (XConfigureEvent*)e;
if(!w->fullscreen){
int rx, ry;
XWindow xw;
if(XTranslateCoordinates(_x.display, w->drawable, DefaultRootWindow(_x.display), 0, 0, &rx, &ry, &xw))
w->windowrect = Rect(rx, ry, rx+xe->width, ry+xe->height);
}
if(xe->width == Dx(w->screenr) && xe->height == Dy(w->screenr))
return 0;
r = Rect(0, 0, xe->width, xe->height);
if(w->screenpm != w->nextscreenpm){
XCopyArea(_x.display, w->screenpm, w->drawable, _x.gccopy, r.min.x, r.min.y,
Dx(r), Dy(r), r.min.x, r.min.y);
XSync(_x.display, False);
}
w->newscreenr = r;
return 1;
}
static int
_xreplacescreenimage(Client *client)
{
Memimage *m;
XDrawable pixmap;
Rectangle r;
Xwin *w;
w = (Xwin*)client->view;
r = w->newscreenr;
pixmap = XCreatePixmap(_x.display, w->drawable, Dx(r), Dy(r), _x.depth);
m = _xallocmemimage(r, _x.chan, pixmap);
if(w->nextscreenpm != w->screenpm)
XFreePixmap(_x.display, w->nextscreenpm);
w->nextscreenpm = pixmap;
w->screenr = r;
client->mouserect = r;
xunlock();
gfx_replacescreenimage(client, m);
xlock();
return 1;
}
void
rpc_resizeimg(Client *client)
{
xlock();
_xreplacescreenimage(client);
xunlock();
}
void
rpc_gfxdrawlock(void)
{
xlock();
}
void
rpc_gfxdrawunlock(void)
{
xunlock();
}
void
rpc_topwin(Client *client)
{
Xwin *w = (Xwin*)client->view;
xlock();
XMapRaised(_x.display, w->drawable);
XSetInputFocus(_x.display, w->drawable, RevertToPointerRoot,
CurrentTime);
XFlush(_x.display);
xunlock();
}
void
rpc_resizewindow(Client *client, Rectangle r)
{
Xwin *w = (Xwin*)client->view;
XWindowChanges e;
int value_mask;
xlock();
memset(&e, 0, sizeof e);
value_mask = CWX|CWY|CWWidth|CWHeight;
e.width = Dx(r);
e.height = Dy(r);
XConfigureWindow(_x.display, w->drawable, value_mask, &e);
XFlush(_x.display);
xunlock();
}
static void
_xmovewindow(Xwin *w, Rectangle r)
{
XWindowChanges e;
int value_mask;
memset(&e, 0, sizeof e);
value_mask = CWX|CWY|CWWidth|CWHeight;
e.x = r.min.x;
e.y = r.min.y;
e.width = Dx(r);
e.height = Dy(r);
XConfigureWindow(_x.display, w->drawable, value_mask, &e);
XFlush(_x.display);
}
static int
_xtoplan9kbd(XEvent *e)
{
KeySym k;
if(e->xany.type != KeyPress)
return -1;
needstack(64*1024); /* X has some *huge* buffers in openobject */
/* and they're even bigger on SuSE */
XLookupString((XKeyEvent*)e,NULL,0,&k,NULL);
if(k == NoSymbol)
return -1;
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_Meta_L: /* Shift Alt on PCs */
case XK_Alt_R:
case XK_Meta_R: /* Shift Alt on PCs */
case XK_Multi_key:
return -1;
default: /* not ISO-1 or tty control */
if(k>0xff) {
k = _p9keysym2ucs(k);
if(k==-1) return -1;
}
}
}
/* 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 &= 0x9f;
if(k == NoSymbol) {
return -1;
}
return k+0;
}
int
_xtoplan9buttons(unsigned int b)
{
if(b == 0){
return 0;
}
return 1<<(b-1);
}
static int
_xtoplan9mouse(Xwin *w, XEvent *e, Mouse *m)
{
XButtonEvent *be;
XMotionEvent *me;
if(_x.putsnarf != _x.assertsnarf){
_x.assertsnarf = _x.putsnarf;
XSetSelectionOwner(_x.display, XA_PRIMARY, w->drawable, CurrentTime);
if(_x.clipboard != None)
XSetSelectionOwner(_x.display, _x.clipboard, w->drawable, CurrentTime);
XFlush(_x.display);
}
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 -1;
/* BUG? on mac need to inherit these from elsewhere? */
m->xy.x = be->x;
m->xy.y = be->y;
m->msec = be->time;
m->buttons |= _xtoplan9buttons(be->button);
break;
case ButtonRelease:
be = (XButtonEvent*)e;
m->xy.x = be->x;
m->xy.y = be->y;
m->msec = be->time;
m->buttons &= ~_xtoplan9buttons(be->button);
case MotionNotify:
me = (XMotionEvent*)e;
m->xy.x = me->x;
m->xy.y = me->y;
m->msec = me->time;
return 0; // do not set buttons
default:
return -1;
}
return 0;
}
void
rpc_setmouse(Client *client, Point p)
{
Xwin *w = (Xwin*)client->view;
xlock();
XWarpPointer(_x.display, None, w->drawable, 0, 0, 0, 0, p.x, p.y);
XFlush(_x.display);
xunlock();
}
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;
}
static void
xcursorarrow(Xwin *w)
{
if(_x.cursor != 0){
XFreeCursor(_x.display, _x.cursor);
_x.cursor = 0;
}
XUndefineCursor(_x.display, w->drawable);
XFlush(_x.display);
}
void
rpc_setcursor(Client *client, Cursor *c, Cursor2 *c2)
{
Xwin *w = (Xwin*)client->view;
XColor fg, bg;
XCursor xc;
Pixmap xsrc, xmask;
int i;
uchar src[2*16], mask[2*16];
USED(c2);
xlock();
if(c == nil){
xcursorarrow(w);
xunlock();
return;
}
for(i=0; i<2*16; i++){
src[i] = revbyte(c->set[i]);
mask[i] = revbyte(c->set[i] | c->clr[i]);
}
fg = _x.map[0];
bg = _x.map[255];
xsrc = XCreateBitmapFromData(_x.display, w->drawable, (char*)src, 16, 16);
xmask = XCreateBitmapFromData(_x.display, w->drawable, (char*)mask, 16, 16);
xc = XCreatePixmapCursor(_x.display, xsrc, xmask, &fg, &bg, -c->offset.x, -c->offset.y);
if(xc != 0) {
XDefineCursor(_x.display, w->drawable, xc);
if(_x.cursor != 0)
XFreeCursor(_x.display, _x.cursor);
_x.cursor = xc;
}
XFreePixmap(_x.display, xsrc);
XFreePixmap(_x.display, xmask);
XFlush(_x.display);
xunlock();
}
struct {
QLock lk;
char buf[SnarfSize];
#ifdef APPLESNARF
Rune rbuf[SnarfSize];
PasteboardRef apple;
#endif
} clip;
static uchar*
_xgetsnarffrom(Xwin *w, XWindow xw, Atom clipboard, Atom target, int timeout0, int timeout)
{
Atom prop, type;
ulong len, lastlen, dummy;
int fmt, i;
uchar *data, *xdata;
/*
* 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.
*/
prop = 1;
XChangeProperty(_x.display, w->drawable, prop, target, 8, PropModeReplace, (uchar*)"", 0);
XConvertSelection(_x.display, clipboard, target, prop, w->drawable, CurrentTime);
XFlush(_x.display);
lastlen = 0;
timeout0 = (timeout0 + 9)/10;
timeout = (timeout + 9)/10;
for(i=0; i<timeout0 || (lastlen!=0 && i<timeout); i++){
usleep(10*1000);
XGetWindowProperty(_x.display, w->drawable, prop, 0, 0, 0, AnyPropertyType,
&type, &fmt, &dummy, &len, &xdata);
if(lastlen == len && len > 0){
XFree(xdata);
break;
}
lastlen = len;
XFree(xdata);
}
if(len == 0)
return nil;
/* get the property */
xdata = nil;
XGetWindowProperty(_x.display, w->drawable, prop, 0, SnarfSize/sizeof(ulong), 0,
AnyPropertyType, &type, &fmt, &len, &dummy, &xdata);
if((type != target && type != XA_STRING && type != _x.utf8string) || len == 0){
if(xdata)
XFree(xdata);
return nil;
}
if(xdata){
data = (uchar*)strdup((char*)xdata);
XFree(xdata);
return data;
}
return nil;
}
char*
rpc_getsnarf(void)
{
uchar *data;
Atom clipboard;
XWindow xw;
Xwin *w;
qlock(&clip.lk);
xlock();
w = _x.windows;
/*
* Have we snarfed recently and the X server hasn't caught up?
*/
if(_x.putsnarf != _x.assertsnarf)
goto mine;
/*
* Is there a primary selection (highlighted text in an xterm)?
*/
clipboard = XA_PRIMARY;
xw = XGetSelectionOwner(_x.display, XA_PRIMARY);
// TODO check more
if(xw == w->drawable){
mine:
data = (uchar*)strdup(clip.buf);
goto out;
}
/*
* If not, is there a clipboard selection?
*/
if(xw == None && _x.clipboard != None){
clipboard = _x.clipboard;
xw = XGetSelectionOwner(_x.display, _x.clipboard);
if(xw == w->drawable)
goto mine;
}
/*
* If not, give up.
*/
if(xw == None){
data = nil;
goto out;
}
if((data = _xgetsnarffrom(w, xw, clipboard, _x.utf8string, 10, 100)) == nil)
if((data = _xgetsnarffrom(w, xw, clipboard, XA_STRING, 10, 100)) == nil){
/* nothing left to do */
}
out:
xunlock();
qunlock(&clip.lk);
return (char*)data;
}
void
__xputsnarf(char *data)
{
XButtonEvent e;
Xwin *w;
if(strlen(data) >= SnarfSize)
return;
qlock(&clip.lk);
xlock();
w = _x.windows;
strcpy(clip.buf, data);
/* leave note for mouse proc to assert selection ownership */
_x.putsnarf++;
/* send mouse a fake event so snarf is announced */
memset(&e, 0, sizeof e);
e.type = ButtonPress;
e.window = w->drawable;
e.state = ~0;
e.button = ~0;
XSendEvent(_x.display, w->drawable, True, ButtonPressMask, (XEvent*)&e);
XFlush(_x.display);
xunlock();
qunlock(&clip.lk);
}
static int
_xselect(XEvent *e)
{
char *name;
XEvent r;
XSelectionRequestEvent *xe;
Atom a[4];
memset(&r, 0, sizeof r);
xe = (XSelectionRequestEvent*)e;
if(0) fprint(2, "xselect target=%d requestor=%d property=%d selection=%d (sizeof atom=%d)\n",
xe->target, xe->requestor, xe->property, xe->selection, sizeof a[0]);
r.xselection.property = xe->property;
if(xe->target == _x.targets){
a[0] = _x.utf8string;
a[1] = XA_STRING;
a[2] = _x.text;
a[3] = _x.compoundtext;
XChangeProperty(_x.display, xe->requestor, xe->property, XA_ATOM,
32, PropModeReplace, (uchar*)a, nelem(a));
}else if(xe->target == XA_STRING
|| xe->target == _x.utf8string
|| xe->target == _x.text
|| xe->target == _x.compoundtext
|| ((name = XGetAtomName(_x.display, xe->target)) && strcmp(name, "text/plain;charset=UTF-8") == 0)){
/* text/plain;charset=UTF-8 seems nonstandard but is used by Synergy */
/* if the target is STRING we're supposed to reply with Latin1 XXX */
qlock(&clip.lk);
XChangeProperty(_x.display, xe->requestor, xe->property, xe->target,
8, PropModeReplace, (uchar*)clip.buf, strlen(clip.buf));
qunlock(&clip.lk);
}else{
if(strcmp(name, "TIMESTAMP") != 0)
fprint(2, "%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(_x.display, xe->requestor, False, 0, &r);
XFlush(_x.display);
return 0;
}
#ifdef APPLESNARF
char*
_applegetsnarf(void)
{
char *s, *t;
CFArrayRef flavors;
CFDataRef data;
CFIndex nflavor, ndata, j;
CFStringRef type;
ItemCount nitem;
PasteboardItemID id;
PasteboardSyncFlags flags;
UInt32 i;
/* fprint(2, "applegetsnarf\n"); */
qlock(&clip.lk);
if(clip.apple == nil){
if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){
fprint(2, "apple pasteboard create failed\n");
qunlock(&clip.lk);
return nil;
}
}
flags = PasteboardSynchronize(clip.apple);
if(flags&kPasteboardClientIsOwner){
s = strdup(clip.buf);
qunlock(&clip.lk);
return s;
}
if(PasteboardGetItemCount(clip.apple, &nitem) != noErr){
fprint(2, "apple pasteboard get item count failed\n");
qunlock(&clip.lk);
return nil;
}
for(i=1; i<=nitem; i++){
if(PasteboardGetItemIdentifier(clip.apple, i, &id) != noErr)
continue;
if(PasteboardCopyItemFlavors(clip.apple, id, &flavors) != noErr)
continue;
nflavor = CFArrayGetCount(flavors);
for(j=0; j<nflavor; j++){
type = (CFStringRef)CFArrayGetValueAtIndex(flavors, j);
if(!UTTypeConformsTo(type, CFSTR("public.utf16-plain-text")))
continue;
if(PasteboardCopyItemFlavorData(clip.apple, id, type, &data) != noErr)
continue;
ndata = CFDataGetLength(data);
qunlock(&clip.lk);
s = smprint("%.*S", ndata/2, (Rune*)CFDataGetBytePtr(data));
CFRelease(flavors);
CFRelease(data);
for(t=s; *t; t++)
if(*t == '\r')
*t = '\n';
return s;
}
CFRelease(flavors);
}
qunlock(&clip.lk);
return nil;
}
void
_appleputsnarf(char *s)
{
CFDataRef cfdata;
PasteboardSyncFlags flags;
/* fprint(2, "appleputsnarf\n"); */
if(strlen(s) >= SnarfSize)
return;
qlock(&clip.lk);
strcpy(clip.buf, s);
runesnprint(clip.rbuf, nelem(clip.rbuf), "%s", s);
if(clip.apple == nil){
if(PasteboardCreate(kPasteboardClipboard, &clip.apple) != noErr){
fprint(2, "apple pasteboard create failed\n");
qunlock(&clip.lk);
return;
}
}
if(PasteboardClear(clip.apple) != noErr){
fprint(2, "apple pasteboard clear failed\n");
qunlock(&clip.lk);
return;
}
flags = PasteboardSynchronize(clip.apple);
if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
fprint(2, "apple pasteboard cannot assert ownership\n");
qunlock(&clip.lk);
return;
}
cfdata = CFDataCreate(kCFAllocatorDefault,
(uchar*)clip.rbuf, runestrlen(clip.rbuf)*2);
if(cfdata == nil){
fprint(2, "apple pasteboard cfdatacreate failed\n");
qunlock(&clip.lk);
return;
}
if(PasteboardPutItemFlavor(clip.apple, (PasteboardItemID)1,
CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
fprint(2, "apple pasteboard putitem failed\n");
CFRelease(cfdata);
qunlock(&clip.lk);
return;
}
/* CFRelease(cfdata); ??? */
qunlock(&clip.lk);
}
#endif /* APPLESNARF */
void
rpc_putsnarf(char *data)
{
#ifdef APPLESNARF
_appleputsnarf(data);
#endif
__xputsnarf(data);
}
/*
* Send the mouse event back to the window manager.
* So that 9term can tell rio to pop up its button3 menu.
*/
void
rpc_bouncemouse(Client *c, Mouse m)
{
Xwin *w = (Xwin*)c->view;
XButtonEvent e;
XWindow dw;
xlock();
e.type = ButtonPress;
e.state = 0;
e.button = 0;
if(m.buttons&1)
e.button = 1;
else if(m.buttons&2)
e.button = 2;
else if(m.buttons&4)
e.button = 3;
e.same_screen = 1;
XTranslateCoordinates(_x.display, w->drawable,
DefaultRootWindow(_x.display),
m.xy.x, m.xy.y, &e.x_root, &e.y_root, &dw);
e.root = DefaultRootWindow(_x.display);
e.window = e.root;
e.subwindow = None;
e.x = e.x_root;
e.y = e.y_root;
#undef time
e.time = CurrentTime;
XUngrabPointer(_x.display, m.msec);
XSendEvent(_x.display, e.root, True, ButtonPressMask, (XEvent*)&e);
XFlush(_x.display);
xunlock();
}