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