xcmdsrv_client.c 13 KB

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