/* * $Id: events.c,v 1.3 2007-06-24 20:32:49 dhmunro Exp $ * X11 event handler */ /* Copyright (c) 2005, The Regents of the University of California. * All rights reserved. * This file is part of yorick (http://yorick.sourceforge.net). * Read the accompanying LICENSE file for details. */ #include "config.h" #include "playx.h" #include "playu.h" #include "pstdlib.h" #include #include #include static void (*xon_expose)(void *c,int *xy)= 0; static void (*xon_destroy)(void *c)= 0; static void (*xon_resize)(void *c,int w,int h)= 0; static void (*xon_focus)(void *c,int in)= 0; static void (*xon_key)(void *c,int k,int md)= 0; static void (*xon_click)(void *c,int b,int md,int x,int y,unsigned long ms)=0; static void (*xon_motion)(void *c,int md,int x,int y)= 0; static void (*xon_deselect)(void *c)= 0; static void x_wirer(x_display *xdpy, int disconnect); static void x_event(void *wsdata); static int x_prepoll(void *wsdata); static int x_keycode(x_display *xdpy, XKeyEvent *xkey, int *pkey, int *pmods); static int x_button(unsigned int button); static int x_modifiers(x_display *xdpy, unsigned int state); static void x_sel_send(x_display *xdpy, p_win *w, XEvent *event); static Bool xmotion_counter(Display *dpy, XEvent *event, char *arg); static Bool xmatch_all(Display *dpy, XEvent *event, char *arg); static Bool xselect_find(Display *dpy, XEvent *event, char *arg); void p_gui(void (*on_expose)(void *c, int *xy), void (*on_destroy)(void *c), void (*on_resize)(void *c,int w,int h), void (*on_focus)(void *c,int in), void (*on_key)(void *c,int k,int md), void (*on_click)(void *c,int b,int md,int x,int y, unsigned long ms), void (*on_motion)(void *c,int md,int x,int y), void (*on_deselect)(void *c), void (*on_panic)(p_scr *s)) { xon_expose = on_expose; xon_destroy = on_destroy; xon_resize = on_resize; xon_focus = on_focus; xon_key = on_key; xon_click = on_click; xon_motion = on_motion; xon_deselect = on_deselect; x_on_panic = on_panic; x_wire_events = &x_wirer; } void p_gui_query(void (**on_expose)(void *c, int *xy), void (**on_destroy)(void *c), void (**on_resize)(void *c,int w,int h), void (**on_focus)(void *c,int in), void (**on_key)(void *c,int k,int md), void (**on_click)(void *c,int b,int md,int x,int y, unsigned long ms), void (**on_motion)(void *c,int md,int x,int y), void (**on_deselect)(void *c), void (**on_panic)(p_scr *s)) { *on_expose = xon_expose; *on_destroy = xon_destroy; *on_resize = xon_resize; *on_focus = xon_focus; *on_key = xon_key; *on_click = xon_click; *on_motion = xon_motion; *on_deselect = xon_deselect; *on_panic = x_on_panic; } static void x_wirer(x_display *xdpy, int disconnect) { if (!disconnect) { u_event_src(ConnectionNumber(xdpy->dpy), &x_event, xdpy); u_prepoll(&x_prepoll, xdpy); } else { u_event_src(ConnectionNumber(xdpy->dpy), (void (*)(void*))0, xdpy); u_prepoll((int (*)(void*))0, xdpy); } } static int x_prepoll(void *wsdata) { x_display *xdpy = wsdata; Display *dpy = xdpy->dpy; if (QLength(dpy)) { x_event(xdpy); return 1; } XFlush(dpy); xdpy->motion_q = 0; /* noop unless XSync has cleared off pending events */ if (p_signalling) p_abort(); return 0; } static void x_event(void *wsdata) { x_display *xdpy = wsdata; Display *dpy = xdpy->dpy; p_win *w = 0; Window xwin; XEvent event; /* X error events trigger poll, but XNextEvent doesn't return them * and can block forever waiting for a true event after calling * the application on_error. Sigh. */ /* XNextEvent(dpy, &event); */ if (!XCheckIfEvent(dpy, &event, &xmatch_all, (char *)0)) return; xwin = event.xany.window; w = x_pwin(xdpy, xwin); if (!w) { /* this window is lost, be a good citizen for confused other client */ if (event.type==SelectionRequest) x_sel_send(xdpy, (p_win*)0, &event); return; } switch (event.type) { case Expose: /* expose triggers all drawing operations */ if (xon_expose) { int xy[4], xx, yy; xy[0] = event.xexpose.x; xy[1] = event.xexpose.y; xy[2] = event.xexpose.x+event.xexpose.width; xy[3] = event.xexpose.y+event.xexpose.height; /* modern window managers generate tons of expose events * during resize operations with count==0 * -- in fact, with "opaque resize", event the newer code * can cause unwanted redraws (maybe no more than one?) * while (event.xexpose.count) { * XWindowEvent(dpy, xwin, ExposureMask, &event); */ while (XCheckWindowEvent(dpy, xwin, ExposureMask, &event)) { if (event.xexpose.xxy[2]) xy[2] = xx; if (yy>xy[3]) xy[3] = yy; } xon_expose(w->context, (xy[0]<=0 && xy[1]<=0 && xy[2]>=w->width && xy[3]>=w->height)? 0 : xy); } break; case ConfigureNotify: /* if test only necessary if SubstructureNotifyMask used someday */ if (event.xconfigure.window == xwin) { int resize = (event.xconfigure.width!=w->width || event.xconfigure.height!=w->height); Window root, parent, *child = 0; unsigned int nchild, wd, ht, bo, dp; int x, y, xw, yw; w->x = event.xconfigure.x; w->y = event.xconfigure.y; /* event.xconfigure returns bogus x and y values * after a resize, although seems okay after a move */ xw = yw = 0; root = None; while (XGetGeometry(dpy, xwin, &root, &x, &y, &wd, &ht, &bo, &dp) && XQueryTree(dpy, xwin, &root, &parent, &child, &nchild)) { if (child) XFree(child); child = 0; xw += x; yw += y; xwin = parent; if (xwin == root) break; } if (child) XFree(child); if (xwin==root) w->x = xw, w->y = yw; if (resize && xon_resize) xon_resize(w->context, (w->width = event.xconfigure.width), (w->height = event.xconfigure.height)); } break; case FocusIn: case FocusOut: if (xon_focus) xon_focus(w->context, event.type==FocusIn); break; case EnterNotify: case LeaveNotify: if (xon_focus) xon_focus(w->context, (event.type==EnterNotify)|2); break; case KeyPress: if (xon_key) { int key, mods; if (x_keycode(xdpy, &event.xkey, &key, &mods)) xon_key(w->context, key, mods); } break; case ButtonPress: case ButtonRelease: if (event.xbutton.same_screen) { if (xon_click) { int btn = x_button(event.xbutton.button); int mods = x_modifiers(xdpy, event.xbutton.state); xon_click(w->context, btn, mods, event.xbutton.x, event.xbutton.y, event.xbutton.time); } } break; case MotionNotify: if (event.xbutton.same_screen) { /* skip this if we've already seen more queued motion events */ if (!xdpy->motion_q) { int x = event.xmotion.x; int y = event.xmotion.y; if (xon_motion) { int mods = x_modifiers(xdpy, event.xmotion.state); xon_motion(w->context, mods, x, y); } /* count number of queued motion events when this one finished * being serviced -- all but final will be skipped */ xdpy->motion_q = 0; XCheckIfEvent(dpy, &event, &xmotion_counter, (char *)&xdpy->motion_q); if (xdpy->motion_q) xdpy->motion_q--; } else { xdpy->motion_q--; } } break; case ClientMessage: if (xon_destroy && event.xclient.format==32 && event.xclient.message_type==xdpy->wm_protocols && event.xclient.data.l[0]==xdpy->wm_delete) { xon_destroy(w->context); p_destroy(w); } break; case DestroyNotify: /* this is equivalent to above ClientMessage for subwindow case */ if (xon_destroy && w->d!=None) { xon_destroy(w->context); p_destroy(w); } break; case SelectionClear: if (xon_deselect) xon_deselect(w->context); break; case SelectionNotify: /* should never get these - handled in p_sel_paste */ break; case SelectionRequest: /* somebody wants our selection */ x_sel_send(xdpy, w, &event); break; default: /* other possibilities are: * StructureNotifyMask: CirculateNotify, GravityNotify, * MapNotify, ReparentNotify, UnmapNotify * (always selected): MappingNotify */ break; } } void p_qclear(void) { x_display *xdpy; Display *dpy = 0; for (xdpy=x_displays ; xdpy ; xdpy=xdpy->next) { dpy = (xdpy && !xdpy->panic)? xdpy->dpy : 0; if (dpy) { XEvent event; /* could use XSync here, but that would be antisocial if another * client has sent us a SelectionRequest which is currently queued */ if (xdpy->sel_owner) p_scopy(xdpy->sel_owner, (char *)0, 0); else if (xdpy->sel_string) x_tmpzap(&xdpy->sel_string); while (XCheckIfEvent(dpy, &event, &xmatch_all, (char *)0)) if (event.type==SelectionRequest) x_sel_send(xdpy, (p_win*)0, &event); } } } static void x_sel_send(x_display *xdpy, p_win *w, XEvent *event) { Window requestor = event->xselectionrequest.requestor; if (xdpy->sel_owner==w && xdpy->sel_string && event->xselectionrequest.selection==XA_PRIMARY && event->xselectionrequest.target==XA_STRING) { int len = 0; if (xdpy->sel_string) while (xdpy->sel_string[len]) len++; event->xselection.property = event->xselectionrequest.property; if (event->xselection.property==None) event->xselection.property = XA_STRING; XChangeProperty(xdpy->dpy, requestor, event->xselection.property, XA_STRING, 8, PropModeReplace, (void *)xdpy->sel_string, len); } else { event->xselection.property = None; } event->type = SelectionNotify; event->xselection.send_event = True; event->xselection.requestor = requestor; event->xselection.selection = XA_PRIMARY; event->xselection.target = XA_STRING; event->xselection.time = event->xselectionrequest.time; XSendEvent(xdpy->dpy, requestor, False, 0L, event); } static int x_keypad[15] = { P_F1, P_F2, P_F3, P_F4, P_HOME, P_LEFT, P_UP, P_RIGHT, P_DOWN, P_PGUP, P_PGDN, P_END, 0, P_INSERT, '\177' }; static int x_keycode(x_display *xdpy, XKeyEvent *xkey, int *pkey, int *pmods) { char buf[16]; KeySym keysym; XComposeStatus compose; int key; int len = XLookupString(xkey, buf, 15, &keysym, &compose); int mods = x_modifiers(xdpy, xkey->state); if (keysym>=XK_KP_Space && keysym<=XK_KP_9) { mods |= P_KEYPAD; if (keysym==XK_KP_Space) key = ' '; else if (keysym>=XK_KP_F1 && keysym<=XK_KP_Delete) key = x_keypad[keysym-XK_KP_F1]; else key = (keysym-XK_KP_Space); } else if (keysym==XK_Home) key = P_HOME; else if (keysym==XK_Left) key = P_LEFT; else if (keysym==XK_Up) key = P_UP; else if (keysym==XK_Right) key = P_RIGHT; else if (keysym==XK_Down) key = P_DOWN; else if (keysym==XK_Prior) key = P_PGUP; else if (keysym==XK_Next) key = P_PGDN; else if (keysym==XK_End) key = P_END; else if (keysym==XK_Insert) key = P_INSERT; else if (keysym>=XK_F1 && keysym<=XK_F35) key = P_F1 + (keysym-XK_F1); else if (len==1) key = buf[0]; else return 0; *pkey = key; *pmods = mods; return 1; } static int x_button(unsigned int button) { int b; /* depressingly stupid, since these really are 1-5 */ if (button==Button1) b = 1; else if (button==Button2) b = 2; else if (button==Button3) b = 3; else if (button==Button4) b = 4; else if (button==Button5) b = 5; else b = 0; return b; } static int x_modifiers(x_display *xdpy, unsigned int state) { int s = 0; if (state&Button1Mask) s |= P_BTN1; if (state&Button2Mask) s |= P_BTN2; if (state&Button3Mask) s |= P_BTN3; if (state&Button4Mask) s |= P_BTN4; if (state&Button5Mask) s |= P_BTN5; if (state&ControlMask) s |= P_CONTROL; if (state&ShiftMask) s |= P_SHIFT; if (state&xdpy->meta_state) s |= P_META; if (state&xdpy->alt_state) s |= P_ALT; return s; } /* ARGSUSED */ static Bool xmatch_all(Display *dpy, XEvent *event, char *arg) { return True; } /* ARGSUSED */ static Bool xmotion_counter(Display *dpy, XEvent *event, char *arg) { int *pn_motion = (int *)arg; if (event->type==MotionNotify) (*pn_motion)+= 1; return False; } int p_scopy(p_win *w, char *string, int n) { int clearing = !string || n<0; x_display *xdpy = w->s->xdpy; x_tmpzap(&xdpy->sel_string); if ((clearing? xdpy->sel_owner==w : xdpy->sel_owner!=w) && !xdpy->panic) { Window xwin; if (clearing) { xdpy->sel_owner = 0; xwin = None; } else { p_win *tmp = xdpy->sel_owner; xdpy->sel_owner = w; xwin = w->d; w = tmp; } /* dehighlighting has to happen here (might be triggered by X event) * - highlighting should be done on return from p_scopy */ if (w && xon_deselect) xon_deselect(w->context); /* O'Reilly vol 1 section 12.4 (Interclient Communications/Selections) * cautions against using CurrentTime here, but in vol 2 under * XSetSelectionOwner, they specifically approve the practice * - since the event that triggers this could in principle have * come from an entirely different input channel, anything * other than CurrentTime here would be very difficult */ XSetSelectionOwner(xdpy->dpy, XA_PRIMARY, xwin, CurrentTime); if (xwin!=None && XGetSelectionOwner(xdpy->dpy, XA_PRIMARY)!=xwin) { xdpy->sel_owner = 0; return 1; } if (p_signalling) p_abort(); } if (!clearing) xdpy->sel_string = n? p_strncat((char *)0, string, n) : p_strcpy(string); return 0; } char * p_spaste(p_win *w) { Window xwin = w->d; x_display *xdpy = w->s->xdpy; Display *dpy = xdpy->dpy; int fd, n, format; XEvent event; Atom type; unsigned long nitems, after; unsigned char *prop = 0; /* if we own the selection, just return it */ if (xdpy->sel_owner) { p_win *ww = xdpy->sel_owner; if (XGetSelectionOwner(dpy, XA_PRIMARY)==ww->d) return xdpy->sel_string; xdpy->sel_owner = 0; } x_tmpzap(&xdpy->sel_string); /* tell selection owner to copy selection to STRING property on xwin */ XConvertSelection(dpy, XA_PRIMARY, XA_STRING, XA_STRING, xwin, CurrentTime); /* wait for the SelectionNotify event to arrive * - if this were guaranteed, wouldn't need u_poll1, but I don't * see how a guarantee of return in finite time can be made... */ n = 0; fd = ConnectionNumber(dpy); while (!XCheckIfEvent(dpy, &event, &xselect_find, (char *)&xwin)) { if ((++n) > 20) return 0; /* give up after at most 4 seconds */ u_poll1(fd, 200); } /* retreve up to 16k characters, while deleting STRING property */ if (XGetWindowProperty(dpy, xwin, XA_STRING, 0L, 4000L, True, XA_STRING, &type, &format, &nitems, &after, &prop)==Success) { if (type==XA_STRING && format==8) xdpy->sel_string = p_strcpy((char *)prop); if (prop) XFree((char *)prop); } if (p_signalling) p_abort(); return xdpy->sel_string; } static Bool xselect_find(Display *dpy, XEvent *event, char *arg) { Window xwin = *((Window *)arg); return (event->type==SelectionNotify && event->xselection.requestor==xwin); }