1 /*
2  * $Id: events.c,v 1.3 2007-06-24 20:32:49 dhmunro Exp $
3  * X11 event handler
4  */
5 /* Copyright (c) 2005, The Regents of the University of California.
6  * All rights reserved.
7  * This file is part of yorick (http://yorick.sourceforge.net).
8  * Read the accompanying LICENSE file for details.
9  */
10 
11 #include "config.h"
12 #include "playx.h"
13 
14 #include "playu.h"
15 #include "pstdlib.h"
16 
17 #include <X11/Xatom.h>
18 #include <X11/Xutil.h>
19 #include <X11/keysym.h>
20 
21 static void (*xon_expose)(void *c,int *xy)= 0;
22 static void (*xon_destroy)(void *c)= 0;
23 static void (*xon_resize)(void *c,int w,int h)= 0;
24 static void (*xon_focus)(void *c,int in)= 0;
25 static void (*xon_key)(void *c,int k,int md)= 0;
26 static void (*xon_click)(void *c,int b,int md,int x,int y,unsigned long ms)=0;
27 static void (*xon_motion)(void *c,int md,int x,int y)= 0;
28 static void (*xon_deselect)(void *c)= 0;
29 
30 static void x_wirer(x_display *xdpy, int disconnect);
31 static void x_event(void *wsdata);
32 static int x_prepoll(void *wsdata);
33 
34 static int x_keycode(x_display *xdpy, XKeyEvent *xkey, int *pkey, int *pmods);
35 static int x_button(unsigned int button);
36 static int x_modifiers(x_display *xdpy, unsigned int state);
37 static void x_sel_send(x_display *xdpy, p_win *w, XEvent *event);
38 static Bool xmotion_counter(Display *dpy, XEvent *event, char *arg);
39 static Bool xmatch_all(Display *dpy, XEvent *event, char *arg);
40 static Bool xselect_find(Display *dpy, XEvent *event, char *arg);
41 
42 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))43 p_gui(void (*on_expose)(void *c, int *xy),
44       void (*on_destroy)(void *c),
45       void (*on_resize)(void *c,int w,int h),
46       void (*on_focus)(void *c,int in),
47       void (*on_key)(void *c,int k,int md),
48       void (*on_click)(void *c,int b,int md,int x,int y,
49                        unsigned long ms),
50       void (*on_motion)(void *c,int md,int x,int y),
51       void (*on_deselect)(void *c),
52       void (*on_panic)(p_scr *s))
53 {
54   xon_expose = on_expose;
55   xon_destroy = on_destroy;
56   xon_resize = on_resize;
57   xon_focus = on_focus;
58   xon_key = on_key;
59   xon_click = on_click;
60   xon_motion = on_motion;
61   xon_deselect = on_deselect;
62   x_on_panic = on_panic;
63   x_wire_events = &x_wirer;
64 }
65 
66 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))67 p_gui_query(void (**on_expose)(void *c, int *xy),
68 	    void (**on_destroy)(void *c),
69 	    void (**on_resize)(void *c,int w,int h),
70 	    void (**on_focus)(void *c,int in),
71 	    void (**on_key)(void *c,int k,int md),
72 	    void (**on_click)(void *c,int b,int md,int x,int y,
73 			      unsigned long ms),
74 	    void (**on_motion)(void *c,int md,int x,int y),
75 	    void (**on_deselect)(void *c),
76 	    void (**on_panic)(p_scr *s))
77 {
78   *on_expose = xon_expose;
79   *on_destroy = xon_destroy;
80   *on_resize = xon_resize;
81   *on_focus = xon_focus;
82   *on_key = xon_key;
83   *on_click = xon_click;
84   *on_motion = xon_motion;
85   *on_deselect = xon_deselect;
86   *on_panic = x_on_panic;
87 }
88 
89 static void
x_wirer(x_display * xdpy,int disconnect)90 x_wirer(x_display *xdpy, int disconnect)
91 {
92   if (!disconnect) {
93     u_event_src(ConnectionNumber(xdpy->dpy), &x_event, xdpy);
94     u_prepoll(&x_prepoll, xdpy);
95   } else {
96     u_event_src(ConnectionNumber(xdpy->dpy), (void (*)(void*))0, xdpy);
97     u_prepoll((int (*)(void*))0, xdpy);
98   }
99 }
100 
101 static int
x_prepoll(void * wsdata)102 x_prepoll(void *wsdata)
103 {
104   x_display *xdpy = wsdata;
105   Display *dpy = xdpy->dpy;
106   if (QLength(dpy)) {
107     x_event(xdpy);
108     return 1;
109   }
110   XFlush(dpy);
111   xdpy->motion_q = 0;  /* noop unless XSync has cleared off pending events */
112   if (p_signalling) p_abort();
113   return 0;
114 }
115 
116 static void
x_event(void * wsdata)117 x_event(void *wsdata)
118 {
119   x_display *xdpy = wsdata;
120   Display *dpy = xdpy->dpy;
121   p_win *w = 0;
122   Window xwin;
123   XEvent event;
124 
125   /* X error events trigger poll, but XNextEvent doesn't return them
126    * and can block forever waiting for a true event after calling
127    * the application on_error.  Sigh. */
128   /* XNextEvent(dpy, &event); */
129   if (!XCheckIfEvent(dpy, &event, &xmatch_all, (char *)0))
130     return;
131   xwin = event.xany.window;
132   w = x_pwin(xdpy, xwin);
133   if (!w) {
134     /* this window is lost, be a good citizen for confused other client */
135     if (event.type==SelectionRequest) x_sel_send(xdpy, (p_win*)0, &event);
136     return;
137   }
138 
139   switch (event.type) {
140   case Expose:
141     /* expose triggers all drawing operations */
142     if (xon_expose) {
143       int xy[4], xx, yy;
144       xy[0] = event.xexpose.x;
145       xy[1] = event.xexpose.y;
146       xy[2] = event.xexpose.x+event.xexpose.width;
147       xy[3] = event.xexpose.y+event.xexpose.height;
148       /* modern window managers generate tons of expose events
149        * during resize operations with count==0
150        * -- in fact, with "opaque resize", event the newer code
151        *    can cause unwanted redraws (maybe no more than one?)
152        * while (event.xexpose.count) {
153        *   XWindowEvent(dpy, xwin, ExposureMask, &event);
154        */
155       while (XCheckWindowEvent(dpy, xwin, ExposureMask, &event)) {
156         if (event.xexpose.x<xy[0]) xy[0] = event.xexpose.x;
157         if (event.xexpose.y<xy[1]) xy[1] = event.xexpose.y;
158         xx = event.xexpose.x+event.xexpose.width;
159         yy = event.xexpose.y+event.xexpose.height;
160         if (xx>xy[2]) xy[2] = xx;
161         if (yy>xy[3]) xy[3] = yy;
162       }
163       xon_expose(w->context, (xy[0]<=0 && xy[1]<=0 && xy[2]>=w->width &&
164                               xy[3]>=w->height)? 0 : xy);
165     }
166     break;
167 
168   case ConfigureNotify:
169     /* if test only necessary if SubstructureNotifyMask used someday */
170     if (event.xconfigure.window == xwin) {
171       int resize = (event.xconfigure.width!=w->width ||
172                    event.xconfigure.height!=w->height);
173       Window root, parent, *child = 0;
174       unsigned int nchild, wd, ht, bo, dp;
175       int x, y, xw, yw;
176       w->x = event.xconfigure.x;
177       w->y = event.xconfigure.y;
178       /* event.xconfigure returns bogus x and y values
179        * after a resize, although seems okay after a move */
180       xw = yw = 0;
181       root = None;
182       while (XGetGeometry(dpy, xwin, &root, &x, &y, &wd, &ht, &bo, &dp) &&
183              XQueryTree(dpy, xwin, &root, &parent, &child, &nchild)) {
184         if (child) XFree(child);
185         child = 0;
186         xw += x;
187         yw += y;
188         xwin = parent;
189         if (xwin == root) break;
190       }
191       if (child) XFree(child);
192       if (xwin==root) w->x = xw, w->y = yw;
193       if (resize && xon_resize)
194         xon_resize(w->context,
195                    (w->width = event.xconfigure.width),
196                    (w->height = event.xconfigure.height));
197     }
198     break;
199 
200   case FocusIn:
201   case FocusOut:
202     if (xon_focus) xon_focus(w->context, event.type==FocusIn);
203     break;
204 
205   case EnterNotify:
206   case LeaveNotify:
207     if (xon_focus) xon_focus(w->context, (event.type==EnterNotify)|2);
208     break;
209 
210   case KeyPress:
211     if (xon_key) {
212       int key, mods;
213       if (x_keycode(xdpy, &event.xkey, &key, &mods))
214         xon_key(w->context, key, mods);
215     }
216     break;
217 
218   case ButtonPress:
219   case ButtonRelease:
220     if (event.xbutton.same_screen) {
221       if (xon_click) {
222         int btn = x_button(event.xbutton.button);
223         int mods = x_modifiers(xdpy, event.xbutton.state);
224         xon_click(w->context, btn, mods,
225                   event.xbutton.x, event.xbutton.y,
226                   event.xbutton.time);
227       }
228     }
229     break;
230 
231   case MotionNotify:
232     if (event.xbutton.same_screen) {
233       /* skip this if we've already seen more queued motion events */
234       if (!xdpy->motion_q) {
235         int x = event.xmotion.x;
236         int y = event.xmotion.y;
237         if (xon_motion) {
238           int mods = x_modifiers(xdpy, event.xmotion.state);
239           xon_motion(w->context, mods, x, y);
240         }
241         /* count number of queued motion events when this one finished
242          * being serviced -- all but final will be skipped */
243         xdpy->motion_q = 0;
244         XCheckIfEvent(dpy, &event, &xmotion_counter,
245                       (char *)&xdpy->motion_q);
246         if (xdpy->motion_q) xdpy->motion_q--;
247       } else {
248         xdpy->motion_q--;
249       }
250     }
251     break;
252 
253   case ClientMessage:
254     if (xon_destroy && event.xclient.format==32 &&
255         event.xclient.message_type==xdpy->wm_protocols &&
256         event.xclient.data.l[0]==xdpy->wm_delete) {
257       xon_destroy(w->context);
258       p_destroy(w);
259     }
260     break;
261 
262   case DestroyNotify:
263     /* this is equivalent to above ClientMessage for subwindow case */
264     if (xon_destroy && w->d!=None) {
265       xon_destroy(w->context);
266       p_destroy(w);
267     }
268     break;
269 
270   case SelectionClear:
271     if (xon_deselect) xon_deselect(w->context);
272     break;
273   case SelectionNotify:
274     /* should never get these - handled in p_sel_paste */
275     break;
276   case SelectionRequest:
277     /* somebody wants our selection */
278     x_sel_send(xdpy, w, &event);
279     break;
280 
281   default:
282     /* other possibilities are:
283      * StructureNotifyMask: CirculateNotify, GravityNotify,
284      *                      MapNotify, ReparentNotify, UnmapNotify
285      * (always selected): MappingNotify */
286     break;
287   }
288 }
289 
290 void
p_qclear(void)291 p_qclear(void)
292 {
293   x_display *xdpy;
294   Display *dpy = 0;
295   for (xdpy=x_displays ; xdpy ; xdpy=xdpy->next) {
296     dpy = (xdpy && !xdpy->panic)? xdpy->dpy : 0;
297     if (dpy) {
298       XEvent event;
299       /* could use XSync here, but that would be antisocial if another
300        * client has sent us a SelectionRequest which is currently queued */
301       if (xdpy->sel_owner) p_scopy(xdpy->sel_owner, (char *)0, 0);
302       else if (xdpy->sel_string) x_tmpzap(&xdpy->sel_string);
303       while (XCheckIfEvent(dpy, &event, &xmatch_all, (char *)0))
304         if (event.type==SelectionRequest)
305           x_sel_send(xdpy, (p_win*)0, &event);
306     }
307   }
308 }
309 
310 static void
x_sel_send(x_display * xdpy,p_win * w,XEvent * event)311 x_sel_send(x_display *xdpy, p_win *w, XEvent *event)
312 {
313   Window requestor = event->xselectionrequest.requestor;
314   if (xdpy->sel_owner==w && xdpy->sel_string &&
315       event->xselectionrequest.selection==XA_PRIMARY &&
316       event->xselectionrequest.target==XA_STRING) {
317     int len = 0;
318     if (xdpy->sel_string) while (xdpy->sel_string[len]) len++;
319     event->xselection.property = event->xselectionrequest.property;
320     if (event->xselection.property==None)
321       event->xselection.property = XA_STRING;
322     XChangeProperty(xdpy->dpy, requestor, event->xselection.property,
323                     XA_STRING, 8, PropModeReplace,
324                     (void *)xdpy->sel_string, len);
325   } else {
326     event->xselection.property = None;
327   }
328   event->type = SelectionNotify;
329   event->xselection.send_event = True;
330   event->xselection.requestor = requestor;
331   event->xselection.selection = XA_PRIMARY;
332   event->xselection.target = XA_STRING;
333   event->xselection.time = event->xselectionrequest.time;
334   XSendEvent(xdpy->dpy, requestor, False, 0L, event);
335 }
336 
337 static int x_keypad[15] = {
338   P_F1, P_F2, P_F3, P_F4, P_HOME, P_LEFT, P_UP, P_RIGHT, P_DOWN,
339   P_PGUP, P_PGDN, P_END, 0, P_INSERT, '\177' };
340 
341 static int
x_keycode(x_display * xdpy,XKeyEvent * xkey,int * pkey,int * pmods)342 x_keycode(x_display *xdpy, XKeyEvent *xkey, int *pkey, int *pmods)
343 {
344   char buf[16];
345   KeySym keysym;
346   XComposeStatus compose;
347   int key;
348   int len = XLookupString(xkey, buf, 15, &keysym, &compose);
349   int mods = x_modifiers(xdpy, xkey->state);
350 
351   if (keysym>=XK_KP_Space && keysym<=XK_KP_9) {
352     mods |= P_KEYPAD;
353     if (keysym==XK_KP_Space)
354       key = ' ';
355     else if (keysym>=XK_KP_F1 && keysym<=XK_KP_Delete)
356       key = x_keypad[keysym-XK_KP_F1];
357     else
358       key = (keysym-XK_KP_Space);
359   } else if (keysym==XK_Home) key = P_HOME;
360   else if (keysym==XK_Left)   key = P_LEFT;
361   else if (keysym==XK_Up)     key = P_UP;
362   else if (keysym==XK_Right)  key = P_RIGHT;
363   else if (keysym==XK_Down)   key = P_DOWN;
364   else if (keysym==XK_Prior)  key = P_PGUP;
365   else if (keysym==XK_Next)   key = P_PGDN;
366   else if (keysym==XK_End)    key = P_END;
367   else if (keysym==XK_Insert) key = P_INSERT;
368   else if (keysym>=XK_F1 && keysym<=XK_F35)
369     key = P_F1 + (keysym-XK_F1);
370   else if (len==1) key = buf[0];
371   else return 0;
372 
373   *pkey = key;
374   *pmods = mods;
375   return 1;
376 }
377 
378 static int
x_button(unsigned int button)379 x_button(unsigned int button)
380 {
381   int b;
382 
383   /* depressingly stupid, since these really are 1-5 */
384   if (button==Button1) b = 1;
385   else if (button==Button2) b = 2;
386   else if (button==Button3) b = 3;
387   else if (button==Button4) b = 4;
388   else if (button==Button5) b = 5;
389   else b = 0;
390 
391   return b;
392 }
393 
394 static int
x_modifiers(x_display * xdpy,unsigned int state)395 x_modifiers(x_display *xdpy, unsigned int state)
396 {
397   int s = 0;
398 
399   if (state&Button1Mask) s |= P_BTN1;
400   if (state&Button2Mask) s |= P_BTN2;
401   if (state&Button3Mask) s |= P_BTN3;
402   if (state&Button4Mask) s |= P_BTN4;
403   if (state&Button5Mask) s |= P_BTN5;
404   if (state&ControlMask) s |= P_CONTROL;
405   if (state&ShiftMask) s |= P_SHIFT;
406   if (state&xdpy->meta_state) s |= P_META;
407   if (state&xdpy->alt_state) s |= P_ALT;
408 
409   return s;
410 }
411 
412 /* ARGSUSED */
413 static Bool
xmatch_all(Display * dpy,XEvent * event,char * arg)414 xmatch_all(Display *dpy, XEvent *event, char *arg)
415 {
416   return True;
417 }
418 
419 /* ARGSUSED */
420 static Bool
xmotion_counter(Display * dpy,XEvent * event,char * arg)421 xmotion_counter(Display *dpy, XEvent *event, char *arg)
422 {
423   int *pn_motion = (int *)arg;
424   if (event->type==MotionNotify) (*pn_motion)+= 1;
425   return False;
426 }
427 
428 int
p_scopy(p_win * w,char * string,int n)429 p_scopy(p_win *w, char *string, int n)
430 {
431   int clearing = !string || n<0;
432   x_display *xdpy = w->s->xdpy;
433   x_tmpzap(&xdpy->sel_string);
434   if ((clearing? xdpy->sel_owner==w : xdpy->sel_owner!=w) && !xdpy->panic) {
435     Window xwin;
436     if (clearing) {
437       xdpy->sel_owner = 0;
438       xwin = None;
439     } else {
440       p_win *tmp = xdpy->sel_owner;
441       xdpy->sel_owner = w;
442       xwin = w->d;
443       w = tmp;
444     }
445 
446     /* dehighlighting has to happen here (might be triggered by X event)
447      * - highlighting should be done on return from p_scopy */
448     if (w && xon_deselect) xon_deselect(w->context);
449 
450     /* O'Reilly vol 1 section 12.4 (Interclient Communications/Selections)
451      * cautions against using CurrentTime here, but in vol 2 under
452      * XSetSelectionOwner, they specifically approve the practice
453      * - since the event that triggers this could in principle have
454      *   come from an entirely different input channel, anything
455      *   other than CurrentTime here would be very difficult */
456     XSetSelectionOwner(xdpy->dpy, XA_PRIMARY, xwin, CurrentTime);
457     if (xwin!=None && XGetSelectionOwner(xdpy->dpy, XA_PRIMARY)!=xwin) {
458       xdpy->sel_owner = 0;
459       return 1;
460     }
461     if (p_signalling) p_abort();
462   }
463 
464   if (!clearing)
465     xdpy->sel_string = n? p_strncat((char *)0, string, n) : p_strcpy(string);
466   return 0;
467 }
468 
469 char *
p_spaste(p_win * w)470 p_spaste(p_win *w)
471 {
472   Window xwin = w->d;
473   x_display *xdpy = w->s->xdpy;
474   Display *dpy = xdpy->dpy;
475   int fd, n, format;
476   XEvent event;
477   Atom type;
478   unsigned long nitems, after;
479   unsigned char *prop = 0;
480 
481   /* if we own the selection, just return it */
482   if (xdpy->sel_owner) {
483     p_win *ww = xdpy->sel_owner;
484     if (XGetSelectionOwner(dpy, XA_PRIMARY)==ww->d)
485       return xdpy->sel_string;
486     xdpy->sel_owner = 0;
487   }
488   x_tmpzap(&xdpy->sel_string);
489 
490   /* tell selection owner to copy selection to STRING property on xwin */
491   XConvertSelection(dpy, XA_PRIMARY, XA_STRING, XA_STRING,
492                     xwin, CurrentTime);
493   /* wait for the SelectionNotify event to arrive
494    * - if this were guaranteed, wouldn't need u_poll1, but I don't
495    *   see how a guarantee of return in finite time can be made... */
496   n = 0;
497   fd = ConnectionNumber(dpy);
498   while (!XCheckIfEvent(dpy, &event, &xselect_find, (char *)&xwin)) {
499     if ((++n) > 20) return 0;  /* give up after at most 4 seconds */
500     u_poll1(fd, 200);
501   }
502 
503   /* retreve up to 16k characters, while deleting STRING property */
504   if (XGetWindowProperty(dpy, xwin, XA_STRING, 0L, 4000L, True, XA_STRING,
505                          &type, &format, &nitems, &after, &prop)==Success) {
506     if (type==XA_STRING && format==8)
507       xdpy->sel_string = p_strcpy((char *)prop);
508     if (prop) XFree((char *)prop);
509   }
510 
511   if (p_signalling) p_abort();
512 
513   return xdpy->sel_string;
514 }
515 
516 static Bool
xselect_find(Display * dpy,XEvent * event,char * arg)517 xselect_find(Display *dpy, XEvent *event, char *arg)
518 {
519   Window xwin = *((Window *)arg);
520   return (event->type==SelectionNotify &&
521           event->xselection.requestor==xwin);
522 }
523