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