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