1/* vi:set ts=8 sts=4 sw=4: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * X-Windows communication by Flemming Madsen 5 * 6 * Do ":help uganda" in Vim to read copying and usage conditions. 7 * Do ":help credits" in Vim to see a list of people who contributed. 8 * See README.txt for an overview of the Vim source code. 9 * 10 * Client for sending commands to an '+xcmdsrv' enabled vim. 11 * This is mostly a de-Vimified version of if_xcmdsrv.c in vim. 12 * See that file for a protocol specification. 13 * 14 * You can make a test program with a Makefile like: 15 * xcmdsrv_client: xcmdsrv_client.c 16 * cc -o $@ -g -DMAIN -I/usr/X11R6/include -L/usr/X11R6/lib $< -lX11 17 * 18 */ 19 20#include <stdio.h> 21#include <string.h> 22#ifdef HAVE_SELECT 23#include <sys/time.h> 24#include <sys/types.h> 25#include <unistd.h> 26#else 27#include <sys/poll.h> 28#endif 29#include <X11/Intrinsic.h> 30#include <X11/Xatom.h> 31 32#define __ARGS(x) x 33 34/* Client API */ 35char * sendToVim __ARGS((Display *dpy, char *name, char *cmd, int asKeys, int *code)); 36 37#ifdef MAIN 38/* A sample program */ 39main(int argc, char **argv) 40{ 41 char *res; 42 int code; 43 44 if (argc == 4) 45 { 46 if ((res = sendToVim(XOpenDisplay(NULL), argv[2], argv[3], 47 argv[1][0] != 'e', &code)) != NULL) 48 { 49 if (code) 50 printf("Error code returned: %d\n", code); 51 puts(res); 52 } 53 exit(0); 54 } 55 else 56 fprintf(stderr, "Usage: %s {k|e} <server> <command>", argv[0]); 57 58 exit(1); 59} 60#endif 61 62/* 63 * Maximum size property that can be read at one time by 64 * this module: 65 */ 66 67#define MAX_PROP_WORDS 100000 68 69/* 70 * Forward declarations for procedures defined later in this file: 71 */ 72 73static int x_error_check __ARGS((Display *dpy, XErrorEvent *error_event)); 74static int AppendPropCarefully __ARGS((Display *display, 75 Window window, Atom property, char *value, int length)); 76static Window LookupName __ARGS((Display *dpy, char *name, 77 int delete, char **loose)); 78static int SendInit __ARGS((Display *dpy)); 79static char *SendEventProc __ARGS((Display *dpy, XEvent *eventPtr, 80 int expect, int *code)); 81static int IsSerialName __ARGS((char *name)); 82 83/* Private variables */ 84static Atom registryProperty = None; 85static Atom commProperty = None; 86static Window commWindow = None; 87static int got_x_error = FALSE; 88 89 90/* 91 * sendToVim -- 92 * Send to an instance of Vim via the X display. 93 * 94 * Results: 95 * A string with the result or NULL. Caller must free if non-NULL 96 */ 97 98 char * 99sendToVim(dpy, name, cmd, asKeys, code) 100 Display *dpy; /* Where to send. */ 101 char *name; /* Where to send. */ 102 char *cmd; /* What to send. */ 103 int asKeys; /* Interpret as keystrokes or expr ? */ 104 int *code; /* Return code. 0 => OK */ 105{ 106 Window w; 107 Atom *plist; 108 XErrorHandler old_handler; 109#define STATIC_SPACE 500 110 char *property, staticSpace[STATIC_SPACE]; 111 int length; 112 int res; 113 static int serial = 0; /* Running count of sent commands. 114 * Used to give each command a 115 * different serial number. */ 116 XEvent event; 117 XPropertyEvent *e = (XPropertyEvent *)&event; 118 time_t start; 119 char *result; 120 char *loosename = NULL; 121 122 if (commProperty == None && dpy != NULL) 123 { 124 if (SendInit(dpy) < 0) 125 return NULL; 126 } 127 128 /* 129 * Bind the server name to a communication window. 130 * 131 * Find any survivor with a serialno attached to the name if the 132 * original registrant of the wanted name is no longer present. 133 * 134 * Delete any lingering names from dead editors. 135 */ 136 137 old_handler = XSetErrorHandler(x_error_check); 138 while (TRUE) 139 { 140 got_x_error = FALSE; 141 w = LookupName(dpy, name, 0, &loosename); 142 /* Check that the window is hot */ 143 if (w != None) 144 { 145 plist = XListProperties(dpy, w, &res); 146 XSync(dpy, False); 147 if (plist != NULL) 148 XFree(plist); 149 if (got_x_error) 150 { 151 LookupName(dpy, loosename ? loosename : name, 152 /*DELETE=*/TRUE, NULL); 153 continue; 154 } 155 } 156 break; 157 } 158 if (w == None) 159 { 160 fprintf(stderr, "no registered server named %s\n", name); 161 return NULL; 162 } 163 else if (loosename != NULL) 164 name = loosename; 165 166 /* 167 * Send the command to target interpreter by appending it to the 168 * comm window in the communication window. 169 */ 170 171 length = strlen(name) + strlen(cmd) + 10; 172 if (length <= STATIC_SPACE) 173 property = staticSpace; 174 else 175 property = (char *) malloc((unsigned) length); 176 177 serial++; 178 sprintf(property, "%c%c%c-n %s%c-s %s", 179 0, asKeys ? 'k' : 'c', 0, name, 0, cmd); 180 if (name == loosename) 181 free(loosename); 182 if (!asKeys) 183 { 184 /* Add a back reference to our comm window */ 185 sprintf(property + length, "%c-r %x %d", 0, (uint) commWindow, serial); 186 length += strlen(property + length + 1) + 1; 187 } 188 189 res = AppendPropCarefully(dpy, w, commProperty, property, length + 1); 190 if (length > STATIC_SPACE) 191 free(property); 192 if (res < 0) 193 { 194 fprintf(stderr, "Failed to send command to the destination program\n"); 195 return NULL; 196 } 197 198 if (asKeys) /* There is no answer for this - Keys are sent async */ 199 return NULL; 200 201 202 /* 203 * Enter a loop processing X events & pooling chars until we see the result 204 */ 205 206#define SEND_MSEC_POLL 50 207 208 time(&start); 209 while ((time((time_t *) 0) - start) < 60) 210 { 211 /* Look out for the answer */ 212#ifndef HAVE_SELECT 213 struct pollfd fds; 214 215 fds.fd = ConnectionNumber(dpy); 216 fds.events = POLLIN; 217 if (poll(&fds, 1, SEND_MSEC_POLL) < 0) 218 break; 219#else 220 fd_set fds; 221 struct timeval tv; 222 223 tv.tv_sec = 0; 224 tv.tv_usec = SEND_MSEC_POLL * 1000; 225 FD_ZERO(&fds); 226 FD_SET(ConnectionNumber(dpy), &fds); 227 if (select(ConnectionNumber(dpy) + 1, &fds, NULL, NULL, &tv) < 0) 228 break; 229#endif 230 while (XEventsQueued(dpy, QueuedAfterReading) > 0) 231 { 232 XNextEvent(dpy, &event); 233 if (event.type == PropertyNotify && e->window == commWindow) 234 if ((result = SendEventProc(dpy, &event, serial, code)) != NULL) 235 return result; 236 } 237 } 238 return NULL; 239} 240 241 242/* 243 * SendInit -- 244 * This procedure is called to initialize the 245 * communication channels for sending commands and 246 * receiving results. 247 */ 248 249 static int 250SendInit(dpy) 251 Display *dpy; 252{ 253 XErrorHandler old_handler; 254 255 /* 256 * Create the window used for communication, and set up an 257 * event handler for it. 258 */ 259 old_handler = XSetErrorHandler(x_error_check); 260 got_x_error = FALSE; 261 262 commProperty = XInternAtom(dpy, "Comm", False); 263 /* Change this back to "InterpRegistry" to talk to tk processes */ 264 registryProperty = XInternAtom(dpy, "VimRegistry", False); 265 266 if (commWindow == None) 267 { 268 commWindow = 269 XCreateSimpleWindow(dpy, XDefaultRootWindow(dpy), 270 getpid(), 0, 10, 10, 0, 271 WhitePixel(dpy, DefaultScreen(dpy)), 272 WhitePixel(dpy, DefaultScreen(dpy))); 273 XSelectInput(dpy, commWindow, PropertyChangeMask); 274 } 275 276 XSync(dpy, False); 277 (void) XSetErrorHandler(old_handler); 278 279 return got_x_error ? -1 : 0; 280} 281 282/* 283 * LookupName -- 284 * Given an interpreter name, see if the name exists in 285 * the interpreter registry for a particular display. 286 * 287 * Results: 288 * If the given name is registered, return the ID of 289 * the window associated with the name. If the name 290 * isn't registered, then return 0. 291 */ 292 293 static Window 294LookupName(dpy, name, delete, loose) 295 Display *dpy; /* Display whose registry to check. */ 296 char *name; /* Name of an interpreter. */ 297 int delete; /* If non-zero, delete info about name. */ 298 char **loose; /* Do another search matching -999 if not found 299 Return result here if a match is found */ 300{ 301 unsigned char *regProp, *entry; 302 unsigned char *p; 303 int result, actualFormat; 304 unsigned long numItems, bytesAfter; 305 Atom actualType; 306 Window returnValue; 307 308 /* 309 * Read the registry property. 310 */ 311 312 regProp = NULL; 313 result = XGetWindowProperty(dpy, RootWindow(dpy, 0), registryProperty, 0, 314 MAX_PROP_WORDS, False, XA_STRING, &actualType, 315 &actualFormat, &numItems, &bytesAfter, 316 ®Prop); 317 318 if (actualType == None) 319 return 0; 320 321 /* 322 * If the property is improperly formed, then delete it. 323 */ 324 325 if ((result != Success) || (actualFormat != 8) || (actualType != XA_STRING)) 326 { 327 if (regProp != NULL) 328 XFree(regProp); 329 XDeleteProperty(dpy, RootWindow(dpy, 0), registryProperty); 330 return 0; 331 } 332 333 /* 334 * Scan the property for the desired name. 335 */ 336 337 returnValue = None; 338 entry = NULL; /* Not needed, but eliminates compiler warning. */ 339 for (p = regProp; (p - regProp) < numItems; ) 340 { 341 entry = p; 342 while ((*p != 0) && (!isspace(*p))) 343 p++; 344 if ((*p != 0) && (strcasecmp(name, p + 1) == 0)) 345 { 346 sscanf(entry, "%x", (uint*) &returnValue); 347 break; 348 } 349 while (*p != 0) 350 p++; 351 p++; 352 } 353 354 if (loose != NULL && returnValue == None && !IsSerialName(name)) 355 { 356 for (p = regProp; (p - regProp) < numItems; ) 357 { 358 entry = p; 359 while ((*p != 0) && (!isspace(*p))) 360 p++; 361 if ((*p != 0) && IsSerialName(p + 1) 362 && (strncmp(name, p + 1, strlen(name)) == 0)) 363 { 364 sscanf(entry, "%x", (uint*) &returnValue); 365 *loose = strdup(p + 1); 366 break; 367 } 368 while (*p != 0) 369 p++; 370 p++; 371 } 372 } 373 374 /* 375 * Delete the property, if that is desired (copy down the 376 * remainder of the registry property to overlay the deleted 377 * info, then rewrite the property). 378 */ 379 380 if ((delete) && (returnValue != None)) 381 { 382 int count; 383 384 while (*p != 0) 385 p++; 386 p++; 387 count = numItems - (p-regProp); 388 if (count > 0) 389 memcpy(entry, p, count); 390 XChangeProperty(dpy, RootWindow(dpy, 0), registryProperty, XA_STRING, 391 8, PropModeReplace, regProp, 392 (int) (numItems - (p-entry))); 393 XSync(dpy, False); 394 } 395 396 XFree(regProp); 397 return returnValue; 398} 399 400 static char * 401SendEventProc(dpy, eventPtr, expected, code) 402 Display *dpy; 403 XEvent *eventPtr; /* Information about event. */ 404 int expected; /* The one were waiting for */ 405 int *code; /* Return code. 0 => OK */ 406{ 407 unsigned char *propInfo; 408 unsigned char *p; 409 int result, actualFormat; 410 int retCode; 411 unsigned long numItems, bytesAfter; 412 Atom actualType; 413 414 if ((eventPtr->xproperty.atom != commProperty) 415 || (eventPtr->xproperty.state != PropertyNewValue)) 416 { 417 return; 418 } 419 420 /* 421 * Read the comm property and delete it. 422 */ 423 424 propInfo = NULL; 425 result = XGetWindowProperty(dpy, commWindow, commProperty, 0, 426 MAX_PROP_WORDS, True, XA_STRING, &actualType, 427 &actualFormat, &numItems, &bytesAfter, 428 &propInfo); 429 430 /* 431 * If the property doesn't exist or is improperly formed 432 * then ignore it. 433 */ 434 435 if ((result != Success) || (actualType != XA_STRING) 436 || (actualFormat != 8)) 437 { 438 if (propInfo != NULL) 439 { 440 XFree(propInfo); 441 } 442 return; 443 } 444 445 /* 446 * Several commands and results could arrive in the property at 447 * one time; each iteration through the outer loop handles a 448 * single command or result. 449 */ 450 451 for (p = propInfo; (p - propInfo) < numItems; ) 452 { 453 /* 454 * Ignore leading NULs; each command or result starts with a 455 * NUL so that no matter how badly formed a preceding command 456 * is, we'll be able to tell that a new command/result is 457 * starting. 458 */ 459 460 if (*p == 0) 461 { 462 p++; 463 continue; 464 } 465 466 if ((*p == 'r') && (p[1] == 0)) 467 { 468 int serial, gotSerial; 469 char *res; 470 471 /* 472 * This is a reply to some command that we sent out. Iterate 473 * over all of its options. Stop when we reach the end of the 474 * property or something that doesn't look like an option. 475 */ 476 477 p += 2; 478 gotSerial = 0; 479 res = ""; 480 retCode = 0; 481 while (((p-propInfo) < numItems) && (*p == '-')) 482 { 483 switch (p[1]) 484 { 485 case 'r': 486 if (p[2] == ' ') 487 res = p + 3; 488 break; 489 case 's': 490 if (sscanf(p + 2, " %d", &serial) == 1) 491 gotSerial = 1; 492 break; 493 case 'c': 494 if (sscanf(p + 2, " %d", &retCode) != 1) 495 retCode = 0; 496 break; 497 } 498 while (*p != 0) 499 p++; 500 p++; 501 } 502 503 if (!gotSerial) 504 continue; 505 506 if (code != NULL) 507 *code = retCode; 508 return serial == expected ? strdup(res) : NULL; 509 } 510 else 511 { 512 /* 513 * Didn't recognize this thing. Just skip through the next 514 * null character and try again. 515 * Also, throw away commands that we cant process anyway. 516 */ 517 518 while (*p != 0) 519 p++; 520 p++; 521 } 522 } 523 XFree(propInfo); 524} 525 526/* 527 * AppendPropCarefully -- 528 * 529 * Append a given property to a given window, but set up 530 * an X error handler so that if the append fails this 531 * procedure can return an error code rather than having 532 * Xlib panic. 533 * 534 * Return: 535 * 0 on OK - -1 on error 536 *-------------------------------------------------------------- 537 */ 538 539 static int 540AppendPropCarefully(dpy, window, property, value, length) 541 Display *dpy; /* Display on which to operate. */ 542 Window window; /* Window whose property is to 543 * be modified. */ 544 Atom property; /* Name of property. */ 545 char *value; /* Characters to append to property. */ 546 int length; /* How much to append */ 547{ 548 XErrorHandler old_handler; 549 550 old_handler = XSetErrorHandler(x_error_check); 551 got_x_error = FALSE; 552 XChangeProperty(dpy, window, property, XA_STRING, 8, 553 PropModeAppend, value, length); 554 XSync(dpy, False); 555 (void) XSetErrorHandler(old_handler); 556 return got_x_error ? -1 : 0; 557} 558 559 560/* 561 * Another X Error handler, just used to check for errors. 562 */ 563/* ARGSUSED */ 564 static int 565x_error_check(dpy, error_event) 566 Display *dpy; 567 XErrorEvent *error_event; 568{ 569 got_x_error = TRUE; 570 return 0; 571} 572 573/* 574 * Check if "str" looks like it had a serial number appended. 575 * Actually just checks if the name ends in a digit. 576 */ 577 static int 578IsSerialName(str) 579 char *str; 580{ 581 int len = strlen(str); 582 583 return (len > 1 && isdigit(str[len - 1])); 584} 585