Eyes.c revision 26df5c7c
1/*
2
3Copyright (c) 1991  X Consortium
4
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
12
13The above copyright notice and this permission notice shall be included
14in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
20OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22OTHER DEALINGS IN THE SOFTWARE.
23
24Except as contained in this notice, the name of the X Consortium shall
25not be used in advertising or otherwise to promote the sale, use or
26other dealings in this Software without prior written authorization
27from the X Consortium.
28
29*/
30
31/*
32 * Eyes.c
33 *
34 * a widget which follows the mouse around
35 */
36
37#ifdef HAVE_CONFIG_H
38# include "config.h"
39#endif
40
41# include <X11/Xos.h>
42# include <stdio.h>
43# include <X11/IntrinsicP.h>
44# include <X11/StringDefs.h>
45# include <X11/Xmu/Converters.h>
46# include "EyesP.h"
47# include <math.h>
48# include <X11/extensions/shape.h>
49# include <X11/Xlibint.h>
50# include <stdlib.h>
51# include <X11/extensions/XInput2.h>
52
53#define offset(field) XtOffsetOf(EyesRec, eyes.field)
54#define goffset(field) XtOffsetOf(WidgetRec, core.field)
55
56static XtResource resources[] = {
57    {(char *) XtNwidth, (char *) XtCWidth, XtRDimension, sizeof(Dimension),
58	goffset(width), XtRImmediate, (XtPointer) 150},
59    {(char *) XtNheight, (char *) XtCHeight, XtRDimension, sizeof(Dimension),
60	goffset(height), XtRImmediate, (XtPointer) 100},
61    {(char *) XtNforeground, (char *) XtCForeground, XtRPixel, sizeof(Pixel),
62        offset(pixel[PART_PUPIL]), XtRString, (char *) XtDefaultForeground},
63    {(char *) XtNbackgroundPixmap, (char *) XtCPixmap, XtRPixmap, sizeof(Pixmap),
64     XtOffsetOf(CoreRec,core.background_pixmap),
65     XtRImmediate, (XtPointer)None},
66    {(char *) XtNoutline, (char *) XtCForeground, XtRPixel, sizeof(Pixel),
67        offset(pixel[PART_OUTLINE]), XtRString, (char *) XtDefaultForeground},
68    {(char *) XtNcenterColor, (char *) XtCBackground, XtRPixel, sizeof (Pixel),
69	offset(pixel[PART_CENTER]), XtRString, (char *) XtDefaultBackground},
70    {(char *) XtNreverseVideo, (char *) XtCReverseVideo, XtRBoolean, sizeof (Boolean),
71	offset (reverse_video), XtRImmediate, (XtPointer) FALSE},
72    {(char *) XtNbackingStore, (char *) XtCBackingStore, (char *) XtRBackingStore, sizeof (int),
73    	offset (backing_store), XtRString, (char *) "default"},
74    {(char *) XtNshapeWindow, (char *) XtCShapeWindow, XtRBoolean, sizeof (Boolean),
75	offset (shape_window), XtRImmediate, (XtPointer) TRUE},
76#ifdef XRENDER
77    {(char *) XtNrender, (char *) XtCBoolean, XtRBoolean, sizeof(Boolean),
78	offset(render), XtRImmediate, (XtPointer) TRUE },
79#endif
80#ifdef PRESENT
81    {(char *) XtNpresent, (char *) XtCBoolean, XtRBoolean, sizeof(Boolean),
82     offset(present), XtRImmediate, (XtPointer) TRUE },
83#endif
84    {(char *) XtNdistance, (char *) XtCBoolean, XtRBoolean, sizeof(Boolean),
85	offset(distance), XtRImmediate, (XtPointer) FALSE },
86};
87
88#undef offset
89#undef goffset
90
91# define EYE_X(n)	((n) * 2.0)
92# define EYE_Y(n)	(0.0)
93# define EYE_OFFSET	(0.1)	/* padding between eyes */
94# define EYE_THICK	(0.175)	/* thickness of eye rim */
95# define BALL_DIAM	(0.3)
96# define BALL_PAD	(0.175)
97# define EYE_DIAM	(2.0 - (EYE_THICK + EYE_OFFSET) * 2)
98# define BALL_DIST	((EYE_DIAM - BALL_DIAM) / 2.0 - BALL_PAD)
99# define W_MIN_X	(-1.0 + EYE_OFFSET)
100# define W_MAX_X	(3.0 - EYE_OFFSET)
101# define W_MIN_Y	(-1.0 + EYE_OFFSET)
102# define W_MAX_Y	(1.0 - EYE_OFFSET)
103
104# define TPOINT_NONE	(-1000)	/* special value meaning "not yet set" */
105# define TPointEqual(a, b)  ((a).x == (b).x && (a).y == (b).y)
106# define XPointEqual(a, b)  ((a).x == (b).x && (a).y == (b).y)
107# define AngleBetween(A, A0, A1) (A0 <= A1 ? A0 <= A && A <= A1 : \
108					     A0 <= A || A <= A1)
109
110static int delays[] = { 50, 100, 200, 400, 0 };
111
112static void ClassInitialize(void)
113{
114    XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore,
115		    NULL, 0 );
116}
117
118WidgetClass eyesWidgetClass = (WidgetClass) &eyesClassRec;
119
120#ifdef PRESENT
121static void CheckPresent(EyesWidget w) {
122    const xcb_query_extension_reply_t 	    *xfixes_ext_reply;
123    const xcb_query_extension_reply_t 	    *damage_ext_reply;
124    const xcb_query_extension_reply_t 	    *present_ext_reply;
125    xcb_xfixes_query_version_cookie_t       xfixes_cookie;
126    xcb_xfixes_query_version_reply_t        *xfixes_reply;
127    xcb_damage_query_version_cookie_t       damage_cookie;
128    xcb_damage_query_version_reply_t        *damage_reply;
129    xcb_present_query_version_cookie_t      present_cookie;
130    xcb_present_query_version_reply_t       *present_reply;
131
132    if (!w->eyes.present)
133	return;
134
135    xcb_prefetch_extension_data(xt_xcb(w), &xcb_xfixes_id);
136    xcb_prefetch_extension_data(xt_xcb(w), &xcb_damage_id);
137    xcb_prefetch_extension_data(xt_xcb(w), &xcb_present_id);
138
139    xfixes_ext_reply = xcb_get_extension_data(xt_xcb(w), &xcb_xfixes_id);
140    damage_ext_reply = xcb_get_extension_data(xt_xcb(w), &xcb_damage_id);
141    present_ext_reply = xcb_get_extension_data(xt_xcb(w), &xcb_present_id);
142    if (xfixes_ext_reply == NULL || !xfixes_ext_reply->present
143	|| damage_ext_reply == NULL || !damage_ext_reply->present
144	|| present_ext_reply == NULL || !present_ext_reply->present)
145    {
146	w->eyes.present = FALSE;
147    }
148
149    if (!w->eyes.present)
150	return;
151
152    /* Now tell the server which versions of the extensions we support */
153    xfixes_cookie = xcb_xfixes_query_version(xt_xcb(w),
154					     XCB_XFIXES_MAJOR_VERSION,
155					     XCB_XFIXES_MINOR_VERSION);
156
157    damage_cookie = xcb_damage_query_version(xt_xcb(w),
158					     XCB_DAMAGE_MAJOR_VERSION,
159					     XCB_DAMAGE_MINOR_VERSION);
160
161    present_cookie = xcb_present_query_version(xt_xcb(w),
162					       XCB_PRESENT_MAJOR_VERSION,
163					       XCB_PRESENT_MINOR_VERSION);
164
165    xfixes_reply = xcb_xfixes_query_version_reply(xt_xcb(w),
166						  xfixes_cookie,
167						  NULL);
168    free(xfixes_reply);
169
170    damage_reply = xcb_damage_query_version_reply(xt_xcb(w),
171						  damage_cookie,
172						  NULL);
173    free(damage_reply);
174
175    present_reply = xcb_present_query_version_reply(xt_xcb(w),
176						    present_cookie,
177						    NULL);
178    free(present_reply);
179}
180
181static void MakePresentData(EyesWidget w) {
182
183    if (!w->eyes.present)
184        return;
185
186    if (!w->eyes.back_buffer) {
187        xcb_create_pixmap(xt_xcb(w),
188                          w->core.depth,
189                          w->eyes.back_buffer = xcb_generate_id(xt_xcb(w)),
190                          XtWindow(w),
191                          w->core.width,
192                          w->core.height);
193    }
194    if (!w->eyes.back_damage) {
195        xcb_damage_create(xt_xcb(w),
196                          w->eyes.back_damage = xcb_generate_id(xt_xcb(w)),
197                          w->eyes.back_buffer,
198                          XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
199        xcb_xfixes_create_region(xt_xcb(w),
200                                 w->eyes.back_region = xcb_generate_id(xt_xcb(w)),
201                                 0, NULL);
202    }
203}
204
205static void UpdatePresent(EyesWidget w) {
206    if (w->eyes.back_buffer) {
207        xcb_damage_subtract(xt_xcb(w),
208                            w->eyes.back_damage,
209                            None,
210                            w->eyes.back_region);
211        xcb_present_pixmap(xt_xcb(w),
212                           XtWindow(w),
213                           w->eyes.back_buffer,
214                           0,
215                           None,
216                           w->eyes.back_region,
217                           0, 0,
218                           None,
219			   None,
220			   None,
221			   0,
222			   0, 1, 0,
223			   0, NULL);
224    }
225}
226
227#endif
228
229#ifdef PRESENT
230#define EyesDrawable(w) (w->eyes.back_buffer ? w->eyes.back_buffer : XtWindow(w))
231#else
232#define EyesDrawable(w) XtWindow(w)
233#endif
234
235static void draw_it_core(EyesWidget w);
236
237static void EyesGeneric(Widget w, XtPointer closure, XEvent *event, Boolean *continue_to_dispatch)
238{
239        draw_it_core((EyesWidget) w);
240}
241
242struct root_listen_list {
243    struct root_listen_list *next;
244    Widget      widget;
245};
246
247static struct root_listen_list *root_listen_list;
248
249static Boolean xi2_dispatcher(XEvent *event) {
250    struct root_listen_list *rll;
251    Boolean was_dispatched = False;
252
253    for (rll = root_listen_list; rll; rll = rll->next) {
254        if (XtDisplay(rll->widget) == event->xany.display) {
255            XtDispatchEventToWidget(rll->widget, event);
256            was_dispatched = True;
257        }
258    }
259    return was_dispatched;
260}
261
262static void select_xi2_events(Widget w)
263{
264    XIEventMask evmasks[1];
265    unsigned char mask1[(XI_LASTEVENT + 7)/8];
266
267    memset(mask1, 0, sizeof(mask1));
268
269    /* select for button and key events from all master devices */
270    XISetMask(mask1, XI_RawMotion);
271
272    evmasks[0].deviceid = XIAllMasterDevices;
273    evmasks[0].mask_len = sizeof(mask1);
274    evmasks[0].mask = mask1;
275
276    XISelectEvents(XtDisplay(w),
277                   RootWindowOfScreen(XtScreen(w)),
278                   evmasks, 1);
279    XtSetEventDispatcher(XtDisplay(w),
280                         GenericEvent,
281                         xi2_dispatcher);
282}
283
284static Boolean xi2_add_root_listener(Widget widget)
285{
286    struct root_listen_list *rll = malloc (sizeof (struct root_listen_list));
287
288    if (!rll)
289        return False;
290    rll->widget = widget;
291    rll->next = root_listen_list;
292    if (!root_listen_list)
293            select_xi2_events(widget);
294    root_listen_list = rll;
295    XtInsertEventTypeHandler(widget, GenericEvent, NULL, EyesGeneric, NULL, XtListHead);
296    return True;
297}
298
299static void xi2_remove_root_listener(Widget widget)
300{
301    struct root_listen_list *rll, **prev;
302
303    for (prev = &root_listen_list; (rll = *prev) != NULL; prev = &rll->next) {
304        if (rll->widget == widget) {
305            *prev = rll->next;
306            free(rll);
307            break;
308        }
309    }
310}
311
312/* Return 1 if XI2 is available, 0 otherwise */
313static int has_xi2(Display *dpy)
314{
315    int major, minor;
316    int rc;
317
318    /* We need at least XI 2.0 */
319    major = 2;
320    minor = 0;
321
322    rc = XIQueryVersion(dpy, &major, &minor);
323    if (rc == BadRequest) {
324	return 0;
325    } else if (rc != Success) {
326        return 0;
327    }
328    return 1;
329}
330
331
332/* ARGSUSED */
333static void Initialize (
334    Widget greq,
335    Widget gnew,
336    ArgList args,
337    Cardinal *num_args)
338{
339    EyesWidget w = (EyesWidget)gnew;
340    XtGCMask	valuemask;
341    XGCValues	myXGCV;
342    int shape_event_base, shape_error_base;
343#ifdef XRENDER
344    enum EyesPart i;
345#endif
346
347    /*
348     * set the colors if reverse video; these are the colors used:
349     *
350     *     background - paper		white
351     *     foreground - text, ticks	black
352     *     border - border		black (foreground)
353     *
354     * This doesn't completely work since the parent has already made up a
355     * border.  Sigh.
356     */
357    if (w->eyes.reverse_video) {
358	Pixel fg = w->eyes.pixel[PART_PUPIL];
359	Pixel bg = w->core.background_pixel;
360
361	if (w->core.border_pixel == fg)
362 	    w->core.border_pixel = bg;
363	if (w->eyes.pixel[PART_OUTLINE] == fg)
364	    w->eyes.pixel[PART_OUTLINE] = bg;
365	if (w->eyes.pixel[PART_CENTER] == bg)
366	    w->eyes.pixel[PART_CENTER] = fg;
367	w->eyes.pixel[PART_PUPIL] = bg;
368	w->core.background_pixel = fg;
369    }
370
371    myXGCV.foreground = w->eyes.pixel[PART_PUPIL];
372    myXGCV.background = w->core.background_pixel;
373    valuemask = GCForeground | GCBackground;
374    w->eyes.gc[PART_PUPIL] = XtGetGC(gnew, valuemask, &myXGCV);
375
376    myXGCV.foreground = w->eyes.pixel[PART_OUTLINE];
377    valuemask = GCForeground | GCBackground;
378    w->eyes.gc[PART_OUTLINE] = XtGetGC(gnew, valuemask, &myXGCV);
379
380    myXGCV.foreground = w->eyes.pixel[PART_CENTER];
381    myXGCV.background = w->eyes.pixel[PART_PUPIL];
382    valuemask = GCForeground | GCBackground;
383    w->eyes.gc[PART_CENTER] = XtGetGC(gnew, valuemask, &myXGCV);
384
385    w->eyes.update = 0;
386    /* wait for Realize to add the timeout */
387    w->eyes.interval_id = 0;
388
389    w->eyes.pupil[0].x = w->eyes.pupil[1].x = TPOINT_NONE;
390    w->eyes.pupil[0].y = w->eyes.pupil[1].y = TPOINT_NONE;
391
392    w->eyes.mouse.x = w->eyes.mouse.y = TPOINT_NONE;
393
394    if (w->eyes.shape_window && !XShapeQueryExtension (XtDisplay (w),
395						       &shape_event_base,
396						       &shape_error_base))
397	w->eyes.shape_window = False;
398    w->eyes.shape_mask = 0;
399    w->eyes.gc[PART_SHAPE] = NULL;
400
401    w->eyes.has_xi2 = has_xi2(XtDisplay(w));
402
403#ifdef XRENDER
404    for (i = 0; i < PART_SHAPE; i ++) {
405	XColor c;
406	XRenderColor rc;
407
408	c.pixel = w->eyes.pixel[i];
409	XQueryColor(XtDisplay (w), w->core.colormap, &c);
410
411	rc.red = c.red;
412	rc.green = c.green;
413	rc.blue = c.blue;
414	rc.alpha = -1;
415	w->eyes.fill[i] = XRenderCreateSolidFill(XtDisplay (w), &rc);
416    }
417#endif
418#ifdef PRESENT
419    w->eyes.back_buffer = None;
420    w->eyes.back_damage = None;
421    CheckPresent(w);
422#endif
423}
424
425static void
426drawEllipse(EyesWidget w, enum EyesPart part,
427	    double centerx, double centery,
428	    double oldx, double oldy,
429	    double diam)
430{
431    const TRectangle tpos = {
432	centerx - diam/2.0,
433	centery - diam/2.0,
434	diam, diam };
435    TRectangle pos;
436    Trectangle(&w->eyes.t, &tpos, &pos);
437
438    if (part == PART_CLEAR) {
439	XFillRectangle(XtDisplay(w), EyesDrawable(w),
440		       w->eyes.gc[PART_CENTER],
441		       (int)pos.x, (int)pos.y,
442		       (int)pos.width+2, (int)pos.height+2);
443	return;
444    }
445#ifdef XRENDER
446    if (w->eyes.render && part != PART_SHAPE && (!w->eyes.shape_window ||
447						 part != PART_OUTLINE) &&
448	w->eyes.picture) {
449	int n, i;
450	double hd, c, s, sx, sy, x, y, px, py;
451	XPointDouble *p;
452
453	pos.x = pos.x + pos.width/2.0;
454	pos.y = pos.y + pos.height/2.0;
455
456	/* determine number of segments to draw */
457	hd = hypot(pos.width, pos.height)/2;
458	n = (M_PI / acos(hd/(hd+1.0))) + 0.5;
459	if (n < 2) n = 2;
460
461	c = cos(M_PI/n);
462	s = sin(M_PI/n);
463	sx = -(pos.width*s)/pos.height;
464	sy = (pos.height*s)/pos.width;
465
466	n *= 2;
467	p = Xmalloc(sizeof(*p)*n);
468	if (!p)
469	    return;
470	x = 0;
471	y = pos.height/2.0;
472	for (i = 0; i < n; i ++)
473	{
474	    p[i].x = x + pos.x;
475	    p[i].y = y + pos.y;
476	    px = x;
477	    py = y;
478	    x = c*px + sx*py;
479	    y = c*py + sy*px;
480	}
481
482	if (oldx != TPOINT_NONE || oldy != TPOINT_NONE)
483	    drawEllipse(w, PART_CLEAR, oldx, oldy,
484			TPOINT_NONE, TPOINT_NONE, diam);
485
486	XRenderCompositeDoublePoly(XtDisplay(w), PictOpOver,
487				   w->eyes.fill[part], w->eyes.picture,
488				   XRenderFindStandardFormat(XtDisplay(w),
489							     PictStandardA8),
490				   0, 0, 0, 0, p, n, 0);
491
492	Xfree(p);
493	return;
494    }
495#endif
496    if (oldx != TPOINT_NONE || oldy != TPOINT_NONE)
497	drawEllipse(w, PART_CLEAR, oldx, oldy,
498		    TPOINT_NONE, TPOINT_NONE, diam);
499
500    XFillArc(XtDisplay(w),
501	     part == PART_SHAPE ? w->eyes.shape_mask : EyesDrawable(w),
502	     w->eyes.gc[part],
503	     (int)(pos.x + 0.5), (int)(pos.y + 0.5),
504	     (int)(pos.width + 0.0), (int)(pos.height + 0.0),
505	     90*64, 360*64);
506}
507
508
509static void
510eyeLiner(EyesWidget	w,
511	 Boolean	draw,
512	 int		num)
513{
514    drawEllipse(w, draw ? PART_OUTLINE : PART_SHAPE,
515		EYE_X(num), EYE_Y(num),
516		TPOINT_NONE, TPOINT_NONE,
517		EYE_DIAM + 2.0*EYE_THICK);
518    if (draw) {
519	drawEllipse(w, PART_CENTER, EYE_X(num), EYE_Y(num),
520		    TPOINT_NONE, TPOINT_NONE,
521		    EYE_DIAM);
522    }
523}
524
525static TPoint computePupil (
526    int		num,
527    TPoint	mouse,
528    const TRectangle *screen)
529{
530	double	cx, cy;
531	double	dist;
532	double	angle;
533	double	dx, dy;
534	double	cosa, sina;
535	TPoint	ret;
536
537	cx = EYE_X(num); dx = mouse.x - cx;
538	cy = EYE_Y(num); dy = mouse.y - cy;
539	if (dx == 0 && dy == 0);
540	else {
541		angle = atan2 ((double) dy, (double) dx);
542		cosa = cos (angle);
543		sina = sin (angle);
544		dist = BALL_DIST;
545		if (screen)
546		{
547		    /* use distance mapping */
548		    double x0, y0, x1, y1;
549		    double a[4];
550		    x0 = screen->x - cx;
551		    y0 = screen->y - cy;
552		    x1 = x0 + screen->width;
553		    y1 = y0 + screen->height;
554		    a[0] = atan2(y0, x0);
555		    a[1] = atan2(y1, x0);
556		    a[2] = atan2(y1, x1);
557		    a[3] = atan2(y0, x1);
558		    if (AngleBetween(angle, a[0], a[1]))
559		    {
560			/* left */
561			dist *= dx / x0;
562		    }
563		    else if (AngleBetween(angle, a[1], a[2]))
564		    {
565			/* bottom */
566			dist *= dy / y1;
567		    }
568		    else if (AngleBetween(angle, a[2], a[3]))
569		    {
570			/* right */
571			dist *= dx / x1;
572		    }
573		    else if (AngleBetween(angle, a[3], a[0]))
574		    {
575			/* top */
576			dist *= dy / y0;
577		    }
578		    if (dist > BALL_DIST)
579			dist = BALL_DIST;
580		}
581		if (dist > hypot ((double) dx, (double) dy)) {
582			cx += dx;
583			cy += dy;
584		} else {
585			cx += dist * cosa;
586			cy += dist * sina;
587		}
588	}
589	ret.x = cx;
590	ret.y = cy;
591	return ret;
592}
593
594static void computePupils (
595    EyesWidget	w,
596    TPoint	mouse,
597    TPoint	pupils[2])
598{
599    TRectangle screen, *sp = NULL;
600    if (w->eyes.distance) {
601	Window r, cw;
602	int x, y;
603	r = RootWindowOfScreen(w->core.screen);
604	XTranslateCoordinates(XtDisplay(w), XtWindow(w), r, 0, 0, &x, &y, &cw);
605	screen.x = Tx(-x, -y, &w->eyes.t);
606	screen.y = Ty(-x, -y, &w->eyes.t);
607	screen.width  = Twidth (w->core.screen->width, w->core.screen->height,
608				&w->eyes.t);
609	screen.height = Theight(w->core.screen->width, w->core.screen->height,
610				&w->eyes.t);
611	sp = &screen;
612    }
613    pupils[0] = computePupil (0, mouse, sp);
614    pupils[1] = computePupil (1, mouse, sp);
615}
616
617static void
618eyeBall(EyesWidget	w,
619	Boolean draw,
620	TPoint	*old,
621	int	num)
622{
623    drawEllipse(w, draw ? PART_PUPIL : PART_CLEAR,
624		w->eyes.pupil[num].x, w->eyes.pupil[num].y,
625		old ? old->x : TPOINT_NONE, old ? old->y : TPOINT_NONE,
626		BALL_DIAM);
627}
628
629static void repaint_window (EyesWidget w)
630{
631	if (XtIsRealized ((Widget) w)) {
632#ifdef PRESENT
633                MakePresentData(w);
634#endif
635		eyeLiner (w, TRUE, 0);
636		eyeLiner (w, TRUE, 1);
637		computePupils (w, w->eyes.mouse, w->eyes.pupil);
638		eyeBall (w, TRUE, NULL, 0);
639		eyeBall (w, TRUE, NULL, 1);
640#ifdef PRESENT
641                UpdatePresent(w);
642#endif
643	}
644}
645
646static void
647drawEye(EyesWidget w, TPoint newpupil, int num)
648{
649    XPoint		xnewpupil, xpupil;
650
651    xpupil.x = Xx(w->eyes.pupil[num].x, w->eyes.pupil[num].y, &w->eyes.t);
652    xpupil.y = Xy(w->eyes.pupil[num].x, w->eyes.pupil[num].y, &w->eyes.t);
653    xnewpupil.x = Xx(newpupil.x, newpupil.y, &w->eyes.t);
654    xnewpupil.y = Xy(newpupil.x, newpupil.y, &w->eyes.t);
655    if (
656#ifdef XRENDER
657	w->eyes.picture ? !TPointEqual(w->eyes.pupil[num], newpupil) :
658#endif
659	!XPointEqual(xpupil, xnewpupil)) {
660	TPoint oldpupil = w->eyes.pupil[num];
661	w->eyes.pupil[num] = newpupil;
662	eyeBall (w, TRUE, &oldpupil, num);
663    }
664}
665
666static void
667drawEyes(EyesWidget w, TPoint mouse)
668{
669    TPoint		newpupil[2];
670    int			num;
671
672#ifdef PRESENT
673    MakePresentData(w);
674#endif
675    if (TPointEqual (mouse, w->eyes.mouse)) {
676	if (delays[w->eyes.update + 1] != 0)
677	    ++w->eyes.update;
678	return;
679    }
680    computePupils (w, mouse, newpupil);
681    for (num = 0; num < 2; num ++) {
682	drawEye(w, newpupil[num], num);
683    }
684
685    w->eyes.mouse = mouse;
686    w->eyes.update = 0;
687#ifdef PRESENT
688    UpdatePresent(w);
689#endif
690}
691
692static void draw_it_core(EyesWidget w)
693{
694    Window		rep_root, rep_child;
695    int			rep_rootx, rep_rooty;
696    unsigned int	rep_mask;
697    int			dx, dy;
698    TPoint		mouse;
699    Display		*dpy = XtDisplay (w);
700    Window		win = XtWindow (w);
701
702    XQueryPointer (dpy, win, &rep_root, &rep_child,
703	    &rep_rootx, &rep_rooty, &dx, &dy, &rep_mask);
704    mouse.x = Tx(dx, dy, &w->eyes.t);
705    mouse.y = Ty(dx, dy, &w->eyes.t);
706
707    drawEyes(w, mouse);
708}
709
710/* ARGSUSED */
711static void draw_it (
712     XtPointer client_data,
713     XtIntervalId *id)		/* unused */
714{
715        EyesWidget	w = (EyesWidget)client_data;
716
717	if (XtIsRealized((Widget)w)) {
718	        draw_it_core(w);
719	}
720        if (!w->eyes.has_xi2) {
721                w->eyes.interval_id =
722                        XtAppAddTimeOut(XtWidgetToApplicationContext((Widget) w),
723                                        delays[w->eyes.update], draw_it, (XtPointer)w);
724        }
725} /* draw_it */
726
727static void Resize (Widget gw)
728{
729    EyesWidget	w = (EyesWidget) gw;
730    XGCValues	xgcv;
731    Widget	parent;
732    Display	*dpy = XtDisplay (w);
733    int		x, y;
734
735    if (XtIsRealized (gw))
736    {
737    	SetTransform (&w->eyes.t,
738		    	0, w->core.width,
739 		    	w->core.height, 0,
740		    	W_MIN_X, W_MAX_X,
741		    	W_MIN_Y, W_MAX_Y);
742#ifdef PRESENT
743        if (w->eyes.back_buffer) {
744                xcb_free_pixmap(xt_xcb(w),
745                                w->eyes.back_buffer);
746                w->eyes.back_buffer = None;
747                xcb_damage_destroy(xt_xcb(w),
748                                   w->eyes.back_damage);
749                w->eyes.back_damage = None;
750        }
751        MakePresentData(w);
752#endif
753        if (EyesDrawable(w) == XtWindow(w))
754                XClearWindow (dpy, XtWindow (w));
755
756#ifdef XRENDER
757	if (w->eyes.picture) {
758	    XRenderFreePicture(dpy, w->eyes.picture);
759	    w->eyes.picture = 0;
760	}
761#endif
762    	if (w->eyes.shape_window) {
763	    w->eyes.shape_mask = XCreatePixmap (dpy, XtWindow (w),
764	    	    w->core.width, w->core.height, 1);
765	    if (!w->eyes.gc[PART_SHAPE])
766		w->eyes.gc[PART_SHAPE] = XCreateGC (dpy, w->eyes.shape_mask,
767						    0, &xgcv);
768	    XSetForeground (dpy, w->eyes.gc[PART_SHAPE], 0);
769	    XFillRectangle (dpy, w->eyes.shape_mask, w->eyes.gc[PART_SHAPE],
770			    0, 0, w->core.width, w->core.height);
771	    XSetForeground (dpy, w->eyes.gc[PART_SHAPE], 1);
772	    eyeLiner (w, FALSE, 0);
773	    eyeLiner (w, FALSE, 1);
774	    x = y = 0;
775	    for (parent = (Widget) w; XtParent (parent); parent = XtParent (parent)) {
776	    	x += parent->core.x + parent->core.border_width;
777	    	x += parent->core.y + parent->core.border_width;
778	    }
779    	    XShapeCombineMask (XtDisplay (parent), XtWindow (parent), ShapeBounding,
780		       	       x, y, w->eyes.shape_mask, ShapeSet);
781	    XFreePixmap (dpy, w->eyes.shape_mask);
782    	}
783#ifdef XRENDER
784	if (w->eyes.render) {
785	    XRenderPictureAttributes pa;
786	    XRenderPictFormat *pf;
787	    pf = XRenderFindVisualFormat(dpy,
788					 DefaultVisualOfScreen(w->core.screen));
789	    if (pf)
790		w->eyes.picture = XRenderCreatePicture(dpy, EyesDrawable (w),
791						       pf, 0, &pa);
792	}
793#endif
794    }
795}
796
797static void Realize (
798     Widget gw,
799     XtValueMask *valueMask,
800     XSetWindowAttributes *attrs)
801{
802    EyesWidget	w = (EyesWidget)gw;
803
804    if (w->eyes.backing_store != Always + WhenMapped + NotUseful) {
805     	attrs->backing_store = w->eyes.backing_store;
806	*valueMask |= CWBackingStore;
807    }
808    XtCreateWindow( gw, (unsigned)InputOutput, (Visual *)CopyFromParent,
809		     *valueMask, attrs );
810    Resize (gw);
811
812    if (w->eyes.has_xi2)
813            xi2_add_root_listener(gw);
814    else
815            w->eyes.interval_id =
816                    XtAppAddTimeOut(XtWidgetToApplicationContext(gw),
817                                    delays[w->eyes.update], draw_it, (XtPointer)gw);
818}
819
820static void Destroy (Widget gw)
821{
822     EyesWidget w = (EyesWidget)gw;
823     int i;
824
825     if (w->eyes.interval_id)
826	XtRemoveTimeOut (w->eyes.interval_id);
827     for (i = 0; i < PART_MAX; i ++)
828	     XtReleaseGC(gw, w->eyes.gc[i]);
829     xi2_remove_root_listener(gw);
830#ifdef XRENDER
831     if (w->eyes.picture)
832	     XRenderFreePicture (XtDisplay(w), w->eyes.picture);
833#endif
834}
835
836/* ARGSUSED */
837static void Redisplay(
838     Widget gw,
839     XEvent *event,
840     Region region)
841{
842    EyesWidget	w;
843
844    w = (EyesWidget) gw;
845    w->eyes.pupil[0].x = TPOINT_NONE;
846    w->eyes.pupil[0].y = TPOINT_NONE;
847    w->eyes.pupil[1].x = TPOINT_NONE;
848    w->eyes.pupil[1].y = TPOINT_NONE;
849    (void) repaint_window ((EyesWidget)gw);
850}
851
852/* ARGSUSED */
853static Boolean SetValues (
854    Widget current,
855    Widget request,
856    Widget new,
857    ArgList args,
858    Cardinal *num_args)
859{
860    return( FALSE );
861}
862
863EyesClassRec eyesClassRec = {
864    { /* core fields */
865    /* superclass		*/	&widgetClassRec,
866    /* class_name		*/	(char *) "Eyes",
867    /* size			*/	sizeof(EyesRec),
868    /* class_initialize		*/	ClassInitialize,
869    /* class_part_initialize	*/	NULL,
870    /* class_inited		*/	FALSE,
871    /* initialize		*/	Initialize,
872    /* initialize_hook		*/	NULL,
873    /* realize			*/	Realize,
874    /* actions			*/	NULL,
875    /* num_actions		*/	0,
876    /* resources		*/	resources,
877    /* num_resources		*/	XtNumber(resources),
878    /* xrm_class		*/	NULLQUARK,
879    /* compress_motion		*/	TRUE,
880    /* compress_exposure	*/	TRUE,
881    /* compress_enterleave	*/	TRUE,
882    /* visible_interest		*/	FALSE,
883    /* destroy			*/	Destroy,
884    /* resize			*/	Resize,
885    /* expose			*/	Redisplay,
886    /* set_values		*/	SetValues,
887    /* set_values_hook		*/	NULL,
888    /* set_values_almost	*/	NULL,
889    /* get_values_hook		*/	NULL,
890    /* accept_focus		*/	NULL,
891    /* version			*/	XtVersion,
892    /* callback_private		*/	NULL,
893    /* tm_table			*/	NULL,
894    /* query_geometry		*/	XtInheritQueryGeometry,
895    }
896};
897