1 #include <assert.h> 2 #include <string.h> 3 #include <rfb/rfb.h> 4 #include <rfb/rfbclient.h> 5 6 #include "nacro.h" 7 8 /* for visual grepping */ 9 typedef struct image_t { 10 int width,height; 11 char* buffer; 12 } image_t; 13 14 /* this is a VNC connection */ 15 typedef struct private_resource_t { 16 int listen_port; 17 rfbScreenInfo* server; 18 rfbClient* client; 19 20 uint32_t keysym; 21 rfbBool keydown; 22 23 int x,y; 24 int buttons; 25 26 char* text_client; 27 char* text_server; 28 29 image_t* grep_image; 30 int x_origin,y_origin; 31 32 enum { SLEEP,VISUALGREP,WAITFORUPDATE } state; 33 result_t result; 34 } private_resource_t; 35 36 /* resource management */ 37 38 #define MAX_RESOURCE_COUNT 20 39 40 static private_resource_t resource_pool[MAX_RESOURCE_COUNT]; 41 static int resource_count=0; 42 43 static private_resource_t* get_resource(int resource) 44 { 45 if(resource>=MAX_RESOURCE_COUNT || resource<0 || resource_pool[resource].client==0) 46 return NULL; 47 return resource_pool+resource; 48 } 49 50 static private_resource_t* get_next_resource(void) 51 { 52 if(resource_count<MAX_RESOURCE_COUNT) { 53 memset(resource_pool+resource_count,0,sizeof(private_resource_t)); 54 resource_count++; 55 return resource_pool+resource_count-1; 56 } else { 57 int i; 58 59 for(i=0;i<MAX_RESOURCE_COUNT && resource_pool[i].client;i++); 60 if(i<MAX_RESOURCE_COUNT) 61 return resource_pool+i; 62 } 63 return NULL; 64 } 65 66 static void free_resource(int resource) 67 { 68 private_resource_t* res=get_resource(resource); 69 if(res) 70 res->client=NULL; 71 } 72 73 /* hooks */ 74 75 static void got_key(rfbBool down,rfbKeySym keysym,rfbClientRec* cl) 76 { 77 private_resource_t* res=(private_resource_t*)cl->screen->screenData; 78 79 res->keydown=down; 80 res->keysym=keysym; 81 res->result|=RESULT_KEY; 82 } 83 84 static void got_mouse(int buttons,int x,int y,rfbClientRec* cl) 85 { 86 private_resource_t* res=(private_resource_t*)cl->screen->screenData; 87 88 res->buttons=buttons; 89 res->x=x; 90 res->y=y; 91 res->result|=RESULT_MOUSE; 92 } 93 94 static void got_text(char* str,int len,rfbClientRec* cl) 95 { 96 private_resource_t* res=(private_resource_t*)cl->screen->screenData; 97 98 if (res->text_client) 99 free(res->text_client); 100 res->text_client=strdup(str); 101 res->result|=RESULT_TEXT_CLIENT; 102 } 103 104 static void got_text_from_server(rfbClient* cl, const char *str, int textlen) 105 { 106 private_resource_t* res=(private_resource_t*)cl->clientData; 107 108 if (res->text_server) 109 free(res->text_server); 110 res->text_server=strdup(str); 111 res->result|=RESULT_TEXT_SERVER; 112 } 113 114 static rfbBool malloc_frame_buffer(rfbClient* cl) 115 { 116 private_resource_t* res=(private_resource_t*)cl->clientData; 117 118 if(!res->server) { 119 int w=cl->width,h=cl->height; 120 121 res->client->frameBuffer=malloc(w*4*h); 122 123 res->server=rfbGetScreen(NULL,NULL,w,h,8,3,4); 124 if(!res->server) 125 return FALSE; 126 res->server->screenData=res; 127 res->server->port=res->listen_port; 128 res->server->frameBuffer=res->client->frameBuffer; 129 res->server->kbdAddEvent=got_key; 130 res->server->ptrAddEvent=got_mouse; 131 res->server->setXCutText=got_text; 132 rfbInitServer(res->server); 133 } else { 134 /* TODO: realloc if necessary */ 135 /* TODO: resolution change: send NewFBSize */ 136 /* TODO: if the origin is out of bounds, reset to 0 */ 137 } 138 } 139 140 static bool_t do_visual_grep(private_resource_t* res,int x,int y,int w,int h) 141 { 142 rfbClient* cl; 143 image_t* image; 144 int x_start,y_start,x_end=x+w-1,y_end=y+h-1; 145 bool_t found=0; 146 147 if(res==0 || (cl=res->client)==0 || (image=res->grep_image)==0) 148 return 0; 149 150 x_start=x-image->width; 151 y_start=y-image->height; 152 if(x_start<0) x_start=0; 153 if(y_start<0) y_start=0; 154 if(x_end+image->width>cl->width) x_end=cl->width-image->width; 155 if(y_end+image->height>cl->height) y_end=cl->height-image->height; 156 157 /* find image and set x_origin,y_origin if found */ 158 for(y=y_start;y<y_end;y++) 159 for(x=x_start;x<x_end;x++) { 160 bool_t matching=1; 161 int i,j; 162 for(j=0;matching && j<image->height;j++) 163 for(i=0;matching && i<image->width;i++) 164 if(memcmp(cl->frameBuffer+4*(x+i+cl->width*(y+j)),image->buffer+4*(i+image->width*j),3)) 165 matching=0; 166 if(matching) { 167 private_resource_t* res=(private_resource_t*)cl->clientData; 168 res->x_origin=x; 169 res->y_origin=y; 170 return -1; 171 } 172 } 173 return 0; 174 } 175 176 static void got_frame_buffer(rfbClient* cl,int x,int y,int w,int h) 177 { 178 private_resource_t* res=(private_resource_t*)cl->clientData; 179 180 assert(res->server); 181 182 if(res->grep_image && do_visual_grep(res,x,y,w,h)) { 183 res->result|=RESULT_FOUNDIMAGE; 184 } 185 if(res->server) { 186 rfbMarkRectAsModified(res->server,x,y,x+w,y+h); 187 } 188 189 res->result|=RESULT_SCREEN; 190 } 191 192 /* init/shutdown functions */ 193 194 resource_t initvnc(const char* server,int server_port,int listen_port) 195 { 196 private_resource_t* res=get_next_resource(); 197 int dummy=0; 198 199 if(res==0) 200 return -1; 201 202 /* remember for later */ 203 res->listen_port=listen_port; 204 205 res->text_client = NULL; 206 res->text_server = NULL; 207 208 res->client=rfbGetClient(8,3,4); 209 res->client->clientData=(void*)res; 210 res->client->GotFrameBufferUpdate=got_frame_buffer; 211 res->client->MallocFrameBuffer=malloc_frame_buffer; 212 res->client->GotXCutText=got_text_from_server; 213 res->client->serverHost=strdup(server); 214 res->client->serverPort=server_port; 215 res->client->appData.encodingsString="raw"; 216 if(!rfbInitClient(res->client,&dummy,NULL)) { 217 res->client=NULL; 218 return -1; 219 } 220 return res-resource_pool; 221 } 222 223 void closevnc(resource_t resource) 224 { 225 private_resource_t* res=get_resource(resource); 226 if(res==0) 227 return; 228 229 if(res->server) 230 rfbScreenCleanup(res->server); 231 232 assert(res->client); 233 234 rfbClientCleanup(res->client); 235 236 res->client=NULL; 237 } 238 239 /* PNM (image) helpers */ 240 241 bool_t savepnm(resource_t resource,const char* filename,int x1,int y1,int x2,int y2) 242 { 243 private_resource_t* res=get_resource(resource); 244 int i,j,w,h; 245 uint32_t* buffer; 246 FILE* f; 247 248 if(res==0 || res->client==0) 249 return 0; 250 assert(res->client->format.depth==24); 251 252 w=res->client->width; 253 h=res->client->height; 254 buffer=(uint32_t*)res->client->frameBuffer; 255 256 if(res==0 || x1>x2 || y1>y2 || x1<0 || x2>=w || y1<0 || y2>=h) 257 return FALSE; 258 259 f=fopen(filename,"wb"); 260 261 if(f==0) 262 return FALSE; 263 264 fprintf(f,"P6\n%d %d\n255\n",1+x2-x1,1+y2-y1); 265 for(j=y1;j<=y2;j++) 266 for(i=x1;i<=x2;i++) { 267 fwrite(buffer+i+j*w,3,1,f); 268 } 269 if(fclose(f)) 270 return FALSE; 271 return TRUE; 272 } 273 274 static image_t* loadpnm(const char* filename) 275 { 276 FILE* f=fopen(filename,"rb"); 277 char buffer[1024]; 278 int i,j,w,h; 279 image_t* image; 280 281 if(f==0) 282 return NULL; 283 284 if(!fgets(buffer,1024,f) || strcmp("P6\n",buffer)) { 285 fclose(f); 286 return NULL; 287 } 288 289 do { 290 fgets(buffer,1024,f); 291 if(feof(f)) { 292 fclose(f); 293 return NULL; 294 } 295 } while(buffer[0]=='#'); 296 297 if( sscanf(buffer,"%d %d",&w,&h)!=2 298 || !fgets(buffer,1024,f) || strcmp("255\n",buffer)) { 299 fclose(f); 300 return NULL; 301 } 302 303 image=(image_t*)malloc(sizeof(image_t)); 304 image->width=w; 305 image->height=h; 306 image->buffer=malloc(w*4*h); 307 if(!image->buffer) { 308 fclose(f); 309 free(image); 310 return NULL; 311 } 312 313 for(j=0;j<h;j++) 314 for(i=0;i<w;i++) 315 if(fread(image->buffer+4*(i+w*j),3,1,f)!=1) { 316 fprintf(stderr,"Could not read 3 bytes at %d,%d\n",i,j); 317 fclose(f); 318 free(image->buffer); 319 free(image); 320 return NULL; 321 } 322 323 fclose(f); 324 325 return image; 326 } 327 328 static void free_image(image_t* image) 329 { 330 if(image->buffer) 331 free(image->buffer); 332 free(image); 333 } 334 335 static void copy_line(rfbScreenInfo *dest, char *backup, 336 int x0, int y0, int x1, int y1, int color_offset) 337 { 338 uint8_t *d = (uint8_t *)dest->frameBuffer, *s = (uint8_t *)backup; 339 int i; 340 int steps0 = x1 > x0 ? x1 - x0 : x0 - x1; 341 int steps1 = y1 > y0 ? y1 - y0 : y0 - y1; 342 343 if (steps1 > steps0) 344 steps0 = steps1; 345 else if (steps0 == 0) 346 steps0 = 1; 347 348 for (i = 0; i <= steps0; i++) { 349 int j, index = 4 * (x0 + i * (x1 - x0) / steps0 350 + dest->width * (y0 + i * (y1 - y0) / steps0)); 351 for (j = 0; j < 4; j++) 352 d[index + j] = s[index + j] + color_offset; 353 } 354 355 rfbMarkRectAsModified(dest, x0 - 5, y0 - 5, x1 + 1, y1 + 2); 356 } 357 358 result_t displaypnm(resource_t resource, const char *filename, 359 coordinate_t x, coordinate_t y, bool_t border, 360 timeout_t timeout_in_seconds) 361 { 362 private_resource_t* res = get_resource(resource); 363 image_t *image; 364 char* fake_frame_buffer; 365 char* backup; 366 int w, h, i, j, w2, h2; 367 result_t result; 368 369 if (res == NULL || res->server == NULL || 370 (image = loadpnm(filename)) == NULL) 371 return 0; 372 373 w = res->server->width; 374 h = res->server->height; 375 fake_frame_buffer = malloc(w * 4 * h); 376 if(!fake_frame_buffer) 377 return 0; 378 memcpy(fake_frame_buffer, res->server->frameBuffer, w * 4 * h); 379 380 backup = res->server->frameBuffer; 381 res->server->frameBuffer = fake_frame_buffer; 382 383 w2 = image->width; 384 if (x + w2 > w) 385 w2 = w - x; 386 h2 = image->height; 387 if (y + h2 > h) 388 h2 = h - y; 389 for (j = 0; j < h2; j++) 390 memcpy(fake_frame_buffer + 4 * (x + (y + j) * w), 391 image->buffer + j * 4 * image->width, 4 * w2); 392 free(image); 393 if (border) { 394 copy_line(res->server, backup, x, y, x + w2, y, 0x80); 395 copy_line(res->server, backup, x, y, x, y + h2, 0x80); 396 copy_line(res->server, backup, x + w2, y, x + w2, y + h2, 0x80); 397 copy_line(res->server, backup, x, y + h2, x + w2, y + h2, 0x80); 398 } 399 rfbMarkRectAsModified(res->server, 400 x - 1, y - 1, x + w2 + 1, y + h2 + 1); 401 402 result = waitforinput(resource, timeout_in_seconds); 403 404 res->server->frameBuffer=backup; 405 free(fake_frame_buffer); 406 rfbMarkRectAsModified(res->server, 407 x - 1, y - 1, x + w2 + 1, y + h2 + 1); 408 409 return result; 410 } 411 412 /* process() and friends */ 413 414 /* this function returns only if res->result in return_mask */ 415 static result_t private_process(resource_t resource,timeout_t timeout_in_seconds,result_t return_mask) 416 { 417 private_resource_t* res=get_resource(resource); 418 fd_set fds; 419 struct timeval tv,tv_start,tv_end; 420 unsigned long timeout=(unsigned long)(timeout_in_seconds*1000000UL); 421 int count,max_fd; 422 423 if(res==0) 424 return 0; 425 426 assert(res->client); 427 428 gettimeofday(&tv_start,NULL); 429 res->result=0; 430 431 do { 432 unsigned long timeout_done; 433 434 if(res->server) { 435 rfbBool loop; 436 do { 437 loop=rfbProcessEvents(res->server,res->server->deferUpdateTime); 438 } while(loop && (res->result&return_mask)==0 439 && rfbIsActive(res->server)); 440 441 if(!rfbIsActive(res->server)) 442 return RESULT_SHUTDOWN; 443 444 if((res->result&return_mask)!=0) 445 return res->result; 446 447 memcpy((char*)&fds,(const char*)&(res->server->allFds),sizeof(fd_set)); 448 max_fd=res->server->maxFd; 449 } else { 450 FD_ZERO(&fds); 451 max_fd=0; 452 } 453 FD_SET(res->client->sock,&fds); 454 if(res->client->sock>max_fd) 455 max_fd=res->client->sock; 456 457 gettimeofday(&tv_end,NULL); 458 timeout_done=tv_end.tv_usec-tv_start.tv_usec+ 459 1000000L*(tv_end.tv_sec-tv_start.tv_sec); 460 if(timeout_done>=timeout) 461 return RESULT_TIMEOUT; 462 463 tv.tv_usec=((timeout-timeout_done)%1000000); 464 tv.tv_sec=(timeout-timeout_done)/1000000; 465 466 count=select(max_fd+1,&fds,NULL,NULL,&tv); 467 if(count<0) 468 return 0; 469 470 if(count>0) { 471 if(FD_ISSET(res->client->sock,&fds)) { 472 if(!HandleRFBServerMessage(res->client)) { 473 closevnc(resource); 474 return 0; 475 } 476 if((res->result&return_mask)!=0) 477 return res->result; 478 } 479 } else { 480 res->result|=RESULT_TIMEOUT; 481 return res->result; 482 } 483 } while(1); 484 485 return RESULT_TIMEOUT; 486 } 487 488 result_t process(resource_t res,timeout_t timeout) 489 { 490 return private_process(res,timeout,RESULT_TIMEOUT); 491 } 492 493 result_t waitforanything(resource_t res,timeout_t timeout) 494 { 495 return private_process(res,timeout,-1); 496 } 497 498 result_t waitforinput(resource_t res,timeout_t timeout) 499 { 500 return private_process(res,timeout,RESULT_KEY|RESULT_MOUSE|RESULT_TIMEOUT); 501 } 502 503 result_t waitforupdate(resource_t res,timeout_t timeout) 504 { 505 return private_process(res,timeout,RESULT_SCREEN|RESULT_TIMEOUT); 506 } 507 508 result_t visualgrep(resource_t resource,const char* filename,timeout_t timeout) 509 { 510 private_resource_t* res=get_resource(resource); 511 image_t* image; 512 result_t result; 513 514 if(res==0 || res->client==0) 515 return 0; 516 517 /* load filename and set res->grep_image to this image */ 518 image=loadpnm(filename); 519 if(image==0) 520 return 0; 521 if(res->grep_image) 522 free_image(res->grep_image); 523 res->grep_image=image; 524 525 if(do_visual_grep(res,0,0,res->client->width,res->client->height)) 526 return RESULT_FOUNDIMAGE; 527 528 result=private_process(resource,timeout,RESULT_FOUNDIMAGE|RESULT_TIMEOUT); 529 530 /* free image */ 531 if(res->grep_image) { 532 free_image(res->grep_image); 533 res->grep_image=NULL; 534 } 535 536 return result; 537 } 538 539 /* auxiliary function for alert */ 540 541 #include "default8x16.h" 542 543 static void center_text(rfbScreenInfo* screen,const char* message,int* x,int* y,int* w,int* h) 544 { 545 rfbFontData* font=&default8x16Font; 546 const char* pointer; 547 int j,x1,y1,x2,y2,line_count=0; 548 if(message==0 || screen==0) 549 return; 550 rfbWholeFontBBox(font,&x1,&y1,&x2,&y2); 551 for(line_count=1,pointer=message;*pointer;pointer++) 552 if(*pointer=='\n') 553 line_count++; 554 555 *h=(y2-y1)*line_count; 556 assert(*h>0); 557 558 if(*h>screen->height) 559 *h=screen->height; 560 561 *x=0; *w=screen->width; *y=(screen->height-*h)/2; 562 563 rfbFillRect(screen,*x,*y,*x+*w,*y+*h,0xff0000); 564 565 for(pointer=message,j=0;j<line_count;j++) { 566 const char* eol; 567 int x_cur,y_cur=*y-y1+j*(y2-y1),width; 568 569 for(width=0,eol=pointer;*eol && *eol!='\n';eol++) 570 width+=rfbWidthOfChar(font,*eol); 571 if(width>screen->width) 572 width=screen->width; 573 574 x_cur=(screen->width-width)/2; 575 for(;pointer!=eol;pointer++) 576 x_cur+=rfbDrawCharWithClip(screen,font, 577 x_cur,y_cur,*pointer, 578 0,0,screen->width,screen->height, 579 0xffffffff,0xffffffff); 580 pointer++; 581 } 582 rfbMarkRectAsModified(screen,*x,*y,*x+*w,*y+*h); 583 } 584 585 /* this is an overlay which is shown for a certain time */ 586 587 result_t alert(resource_t resource,const char* message,timeout_t timeout) 588 { 589 private_resource_t* res=get_resource(resource); 590 char* fake_frame_buffer; 591 char* backup; 592 int x,y,w,h; 593 result_t result; 594 595 if(res == NULL || res->server==NULL) 596 return -1; 597 598 w=res->server->width; 599 h=res->server->height; 600 601 fake_frame_buffer=malloc(w*4*h); 602 if(!fake_frame_buffer) 603 return -1; 604 memcpy(fake_frame_buffer,res->server->frameBuffer,w*4*h); 605 606 backup=res->server->frameBuffer; 607 res->server->frameBuffer=fake_frame_buffer; 608 center_text(res->server,message,&x,&y,&w,&h); 609 fprintf(stderr,"%s\n",message); 610 611 result=waitforinput(resource,timeout); 612 613 res->server->frameBuffer=backup; 614 free(fake_frame_buffer); 615 rfbMarkRectAsModified(res->server,x,y,x+w,y+h); 616 617 return result; 618 } 619 /* inspect last events */ 620 621 keysym_t getkeysym(resource_t res) 622 { 623 private_resource_t* r=get_resource(res); 624 return r->keysym; 625 } 626 627 bool_t getkeydown(resource_t res) 628 { 629 private_resource_t* r=get_resource(res); 630 return r->keydown; 631 } 632 633 coordinate_t getx(resource_t res) 634 { 635 private_resource_t* r=get_resource(res); 636 return r->x; 637 } 638 639 coordinate_t gety(resource_t res) 640 { 641 private_resource_t* r=get_resource(res); 642 return r->y; 643 } 644 645 buttons_t getbuttons(resource_t res) 646 { 647 private_resource_t* r=get_resource(res); 648 return r->buttons; 649 } 650 651 const char *gettext_client(resource_t res) 652 { 653 private_resource_t* r=get_resource(res); 654 return r->text_client; 655 } 656 657 result_t rubberband(resource_t resource, coordinate_t x0, coordinate_t y0) 658 { 659 private_resource_t* res=get_resource(resource); 660 char* fake_frame_buffer; 661 char* backup; 662 int w, h, x, y; 663 664 if(res == NULL || res->server==NULL) 665 return -1; 666 667 x = res->x; 668 y = res->y; 669 w = res->server->width; 670 h = res->server->height; 671 fake_frame_buffer = malloc(w * 4 * h); 672 if(!fake_frame_buffer) 673 return 0; 674 memcpy(fake_frame_buffer, res->server->frameBuffer, w * 4 * h); 675 676 backup = res->server->frameBuffer; 677 res->server->frameBuffer = fake_frame_buffer; 678 679 while (res->buttons) { 680 result_t r = waitforinput(resource, 1000000L); 681 if (x == res->x && y == res->y) 682 continue; 683 copy_line(res->server, backup, x0, y0, x, y0, 0); 684 copy_line(res->server, backup, x0, y0, x0, y, 0); 685 copy_line(res->server, backup, x, y0, x, y, 0); 686 copy_line(res->server, backup, x0, y, x, y, 0); 687 x = res->x; 688 y = res->y; 689 copy_line(res->server, backup, x0, y0, x, y0, 0x80); 690 copy_line(res->server, backup, x0, y0, x0, y, 0x80); 691 copy_line(res->server, backup, x, y0, x, y, 0x80); 692 copy_line(res->server, backup, x0, y, x, y, 0x80); 693 } 694 695 copy_line(res->server, backup, x0, y0, x, y0, 0); 696 copy_line(res->server, backup, x0, y0, x0, y, 0); 697 copy_line(res->server, backup, x, y0, x, y, 0); 698 copy_line(res->server, backup, x0, y, x, y, 0); 699 700 res->server->frameBuffer=backup; 701 free(fake_frame_buffer); 702 703 return RESULT_MOUSE; 704 } 705 706 const char *gettext_server(resource_t res) 707 { 708 private_resource_t* r=get_resource(res); 709 return r->text_server; 710 } 711 712 /* send events to the server */ 713 714 bool_t sendkey(resource_t res,keysym_t keysym,bool_t keydown) 715 { 716 private_resource_t* r=get_resource(res); 717 if(r==NULL) 718 return 0; 719 return SendKeyEvent(r->client,keysym,keydown); 720 } 721 722 bool_t sendascii(resource_t res,const char *string) 723 { 724 timeout_t delay = 0.1; 725 private_resource_t* r=get_resource(res); 726 int i; 727 if(r==NULL) 728 return 0; 729 while (*string) { 730 int keysym = *string; 731 int need_shift = 0; 732 733 if (keysym >= 8 && keysym < ' ') 734 keysym += 0xff00; 735 else if (keysym >= 'A' && keysym <= 'Z') 736 need_shift = 1; 737 else if (keysym > '~') { 738 fprintf(stderr, "String contains non-ASCII " 739 "character 0x%02x\n", *string); 740 return FALSE; 741 } 742 743 if (need_shift) { 744 if (!SendKeyEvent(r->client,0xffe1,1)) 745 return FALSE; 746 waitforinput(r,delay); 747 } 748 for (i = 1; i >= 0; i--) { 749 if (!SendKeyEvent(r->client,keysym,i)) 750 return FALSE; 751 waitforinput(r,delay); 752 } 753 if (need_shift) { 754 if (!SendKeyEvent(r->client,0xffe1,0)) 755 return FALSE; 756 waitforinput(r,delay); 757 } 758 string++; 759 } 760 return TRUE; 761 } 762 763 bool_t sendmouse(resource_t res,coordinate_t x,coordinate_t y,buttons_t buttons) 764 { 765 private_resource_t* r=get_resource(res); 766 if(r==NULL) 767 return 0; 768 return SendPointerEvent(r->client,x,y,buttons); 769 } 770 771 bool_t sendtext(resource_t res, const char *string) 772 { 773 private_resource_t* r=get_resource(res); 774 if(r==NULL) 775 return 0; 776 return SendClientCutText(r->client, (char *)string, (int)strlen(string)); 777 } 778 779 bool_t sendtext_to_server(resource_t res, const char *string) 780 { 781 private_resource_t* r=get_resource(res); 782 if(r==NULL) 783 return 0; 784 rfbSendServerCutText(r->server, (char *)string, (int)strlen(string)); 785 return 1; 786 } 787 788 /* for visual grepping */ 789 790 coordinate_t getxorigin(resource_t res) 791 { 792 private_resource_t* r=get_resource(res); 793 return r->x_origin; 794 } 795 796 coordinate_t getyorigin(resource_t res) 797 { 798 private_resource_t* r=get_resource(res); 799 return r->y_origin; 800 } 801 802