UNIXworkcode

1 /******************************************************************************* 2 * * 3 * nc.c -- Nirvana Editor client program for nedit server processes * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute version of this program linked to * 11 * Motif or Open Motif. See README for details. * 12 * * 13 * This software is distributed in the hope that it will be useful, but WITHOUT * 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 16 * for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License along with * 19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 20 * Place, Suite 330, Boston, MA 02111-1307 USA * 21 * * 22 * Nirvana Text Editor * 23 * November, 1995 * 24 * * 25 * Written by Mark Edel * 26 * * 27 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 33 #include "server_common.h" 34 #include "../util/fileUtils.h" 35 #include "../util/utils.h" 36 #include "../util/prefFile.h" 37 #include "../util/system.h" 38 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <limits.h> 42 #include <string.h> 43 #ifndef __MVS__ 44 #include <sys/param.h> 45 #endif 46 #include <sys/types.h> 47 #include <sys/utsname.h> 48 #include <unistd.h> 49 #include <pwd.h> 50 #include "../util/clearcase.h" 51 #ifdef __EMX__ 52 #include <process.h> 53 #endif 54 55 #include <X11/Intrinsic.h> 56 #include <X11/Xatom.h> 57 58 #ifdef HAVE_DEBUG_H 59 #include "../debug.h" 60 #endif 61 62 #define APP_NAME "xnc" 63 #define APP_CLASS "XNEditClient" 64 65 #define PROPERTY_CHANGE_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */ 66 #define SERVER_START_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */ 67 #define REQUEST_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */ 68 #define FILE_OPEN_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */ 69 70 typedef struct 71 { 72 char* shell; 73 char* serverRequest; 74 } CommandLine; 75 76 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id); 77 static int startServer(const char *message, const char *commandLine); 78 static CommandLine processCommandLine(int argc, char** argv); 79 static void parseCommandLine(int argc, char **arg, CommandLine *cmdLine); 80 static void nextArg(int argc, char **argv, int *argIndex); 81 static void copyCommandLineArg(CommandLine *cmdLine, const char *arg); 82 static void printNcVersion(void); 83 static Boolean findExistingServer(XtAppContext context, 84 Window rootWindow, 85 Atom serverExistsAtom); 86 static void startNewServer(XtAppContext context, 87 Window rootWindow, 88 char* commandLine, 89 Atom serverExistsAtom); 90 static void waitUntilRequestProcessed(XtAppContext context, 91 Window rootWindow, 92 char* commandString, 93 Atom serverRequestAtom); 94 static void waitUntilFilesOpenedOrClosed(XtAppContext context, 95 Window rootWindow); 96 97 Display *TheDisplay; 98 XtAppContext AppContext; 99 static Atom currentWaitForAtom; 100 static Atom noAtom = (Atom)(-1); 101 102 static const char cmdLineHelp[] = 103 "Usage: xnc [-read] [-create]\n" 104 " [-line n | +n] [-do command] [-lm languagemode]\n" 105 " [-svrname name] [-svrcmd command]\n" 106 " [-ask] [-noask] [-timeout seconds]\n" 107 " [-geometry geometry | -g geometry] [-icon | -iconic]\n" 108 " [-tabbed] [-untabbed] [-group] [-wait]\n" 109 " [-V | -version] [-h|-help]\n" 110 " [-xrm resourcestring] [-display [host]:server[.screen]]\n" 111 " [--] [file...]\n"; 112 113 /* Structure to hold X Resource values */ 114 static struct { 115 int autoStart; 116 char serverCmd[2*MAXPATHLEN]; /* holds executable name + flags */ 117 char serverName[MAXPATHLEN]; 118 int waitForClose; 119 int timeOut; 120 } Preferences; 121 122 /* Application resources */ 123 static PrefDescripRec PrefDescrip[] = { 124 {"autoStart", "AutoStart", PREF_BOOLEAN, "True", 125 &Preferences.autoStart, NULL, True}, 126 {"serverCommand", "ServerCommand", PREF_STRING, "xnedit -server -bgrun", 127 Preferences.serverCmd, (void *)sizeof(Preferences.serverCmd), False}, 128 {"serverName", "serverName", PREF_STRING, "", Preferences.serverName, 129 (void *)sizeof(Preferences.serverName), False}, 130 {"waitForClose", "WaitForClose", PREF_BOOLEAN, "False", 131 &Preferences.waitForClose, NULL, False}, 132 {"timeOut", "TimeOut", PREF_INT, "10", 133 &Preferences.timeOut, NULL, False} 134 }; 135 136 /* Resource related command line options */ 137 static XrmOptionDescRec OpTable[] = { 138 {"-ask", ".autoStart", XrmoptionNoArg, (caddr_t)"False"}, 139 {"-noask", ".autoStart", XrmoptionNoArg, (caddr_t)"True"}, 140 {"-svrname", ".serverName", XrmoptionSepArg, (caddr_t)NULL}, 141 {"-svrcmd", ".serverCommand", XrmoptionSepArg, (caddr_t)NULL}, 142 {"-wait", ".waitForClose", XrmoptionNoArg, (caddr_t)"True"}, 143 {"-timeout", ".timeOut", XrmoptionSepArg, (caddr_t)NULL} 144 }; 145 146 /* Struct to hold info about files being opened and edited. */ 147 typedef struct _FileListEntry { 148 Atom waitForFileOpenAtom; 149 Atom waitForFileClosedAtom; 150 char* path; 151 struct _FileListEntry *next; 152 } FileListEntry; 153 154 typedef struct { 155 int waitForOpenCount; 156 int waitForCloseCount; 157 FileListEntry* fileList; 158 } FileListHead; 159 static FileListHead fileListHead; 160 161 static void setPropertyValue(Atom atom) { 162 XChangeProperty(TheDisplay, 163 RootWindow(TheDisplay, DefaultScreen(TheDisplay)), 164 atom, XA_STRING, 165 8, PropModeReplace, 166 (unsigned char *)"True", 4); 167 } 168 169 /* Add another entry to the file entry list, if it doesn't exist yet. */ 170 static void addToFileList(const char *path) 171 { 172 FileListEntry *item; 173 174 /* see if the file already exists in the list */ 175 for (item = fileListHead.fileList; item; item = item->next) { 176 if (!strcmp(item->path, path)) 177 break; 178 } 179 180 /* Add the atom to the head of the file list if it wasn't found. */ 181 if (item == 0) { 182 item = malloc(sizeof(item[0])); 183 item->waitForFileOpenAtom = None; 184 item->waitForFileClosedAtom = None; 185 item->path = (char*)malloc(strlen(path)+1); 186 strcpy(item->path, path); 187 item->next = fileListHead.fileList; 188 fileListHead.fileList = item; 189 } 190 } 191 192 /* Creates the properties for the various paths */ 193 static void createWaitProperties(void) 194 { 195 FileListEntry *item; 196 197 for (item = fileListHead.fileList; item; item = item->next) { 198 fileListHead.waitForOpenCount++; 199 item->waitForFileOpenAtom = 200 CreateServerFileOpenAtom(Preferences.serverName, item->path); 201 setPropertyValue(item->waitForFileOpenAtom); 202 203 if (Preferences.waitForClose == True) { 204 fileListHead.waitForCloseCount++; 205 item->waitForFileClosedAtom = 206 CreateServerFileClosedAtom(Preferences.serverName, 207 item->path, 208 False); 209 setPropertyValue(item->waitForFileClosedAtom); 210 } 211 } 212 } 213 214 int main(int argc, char **argv) 215 { 216 XtAppContext context; 217 Window rootWindow; 218 CommandLine commandLine; 219 Atom serverExistsAtom, serverRequestAtom; 220 XrmDatabase prefDB; 221 Boolean serverExists; 222 223 /* Initialize toolkit and get an application context */ 224 XtToolkitInitialize(); 225 AppContext = context = XtCreateApplicationContext(); 226 227 #ifdef __EMX__ 228 /* expand wildcards if necessary */ 229 _wildcard(&argc, &argv); 230 #endif 231 232 /* Read the preferences command line into a database (note that we 233 don't support the .nc file anymore) */ 234 prefDB = CreatePreferencesDatabase(NULL, APP_CLASS, 235 OpTable, XtNumber(OpTable), (unsigned *)&argc, argv); 236 237 /* Process the command line before calling XtOpenDisplay, because the 238 latter consumes certain command line arguments that we still need 239 (-icon, -geometry ...) */ 240 commandLine = processCommandLine(argc, argv); 241 242 /* Open the display and find the root window */ 243 TheDisplay = XtOpenDisplay (context, NULL, APP_NAME, APP_CLASS, NULL, 244 0, &argc, argv); 245 if (!TheDisplay) { 246 XtWarning ("xnc: Can''t open display\n"); 247 exit(EXIT_FAILURE); 248 } 249 rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay)); 250 251 /* Read the application resources into the Preferences data structure */ 252 RestorePreferences(prefDB, XtDatabase(TheDisplay), APP_NAME, 253 APP_CLASS, PrefDescrip, XtNumber(PrefDescrip)); 254 255 /* Make sure that the time out unit is at least 1 second and not too 256 large either (overflow!). */ 257 if (Preferences.timeOut < 1) { 258 Preferences.timeOut = 1; 259 } else if (Preferences.timeOut > 1000) { 260 Preferences.timeOut = 1000; 261 } 262 263 /* For Clearcase users who have not set a server name, use the clearcase 264 view name. Clearcase views make files with the same absolute path names 265 but different contents (and therefore can't be edited in the same nedit 266 session). This should have no bad side-effects for non-clearcase users */ 267 if (Preferences.serverName[0] == '\0') { 268 const char* viewTag = GetClearCaseViewTag(); 269 if (viewTag != NULL && strlen(viewTag) < MAXPATHLEN) { 270 strcpy(Preferences.serverName, viewTag); 271 } 272 } 273 274 /* Create the wait properties for the various files. */ 275 createWaitProperties(); 276 277 /* Monitor the properties on the root window */ 278 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask); 279 280 /* Create the server property atoms on the current DISPLAY. */ 281 CreateServerPropertyAtoms(Preferences.serverName, 282 &serverExistsAtom, 283 &serverRequestAtom); 284 285 serverExists = findExistingServer(context, 286 rootWindow, 287 serverExistsAtom); 288 289 if (serverExists == False) 290 startNewServer(context, rootWindow, commandLine.shell, serverExistsAtom); 291 292 waitUntilRequestProcessed(context, 293 rootWindow, 294 commandLine.serverRequest, 295 serverRequestAtom); 296 297 waitUntilFilesOpenedOrClosed(context, rootWindow); 298 299 XtCloseDisplay(TheDisplay); 300 XtFree(commandLine.shell); 301 XtFree(commandLine.serverRequest); 302 return 0; 303 } 304 305 306 /* 307 ** Xt timer procedure for timeouts on NEdit server requests 308 */ 309 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id) 310 { 311 /* NOTE: XtAppNextEvent() does call this routine but 312 ** doesn't return unless there are more events. 313 ** Hence, we generate this (synthetic) event to break the deadlock 314 */ 315 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay)); 316 if (currentWaitForAtom != noAtom) { 317 XChangeProperty(TheDisplay, rootWindow, currentWaitForAtom, XA_STRING, 318 8, PropModeReplace, (unsigned char *)"", strlen("")); 319 } 320 321 /* Flag that the timeout has occurred. */ 322 *timeOutReturn = True; 323 } 324 325 326 327 static Boolean findExistingServer(XtAppContext context, 328 Window rootWindow, 329 Atom serverExistsAtom) 330 { 331 Boolean serverExists = True; 332 unsigned char *propValue; 333 int getFmt; 334 Atom dummyAtom; 335 unsigned long dummyULong, nItems; 336 337 /* See if there might be a server (not a guaranty), by translating the 338 root window property XNEDIT_SERVER_EXISTS_<user>_<host> */ 339 if (XGetWindowProperty(TheDisplay, rootWindow, serverExistsAtom, 0, 340 INT_MAX, False, XA_STRING, &dummyAtom, &getFmt, &nItems, 341 &dummyULong, &propValue) != Success || nItems == 0) { 342 serverExists = False; 343 } else { 344 Boolean timeOut = False; 345 XtIntervalId timerId; 346 347 XFree(propValue); 348 349 /* Remove the server exists property to make sure the server is 350 ** running. If it is running it will get recreated. 351 */ 352 XDeleteProperty(TheDisplay, rootWindow, serverExistsAtom); 353 XSync(TheDisplay, False); 354 timerId = XtAppAddTimeOut(context, 355 PROPERTY_CHANGE_TIMEOUT, 356 (XtTimerCallbackProc)timeOutProc, 357 &timeOut); 358 currentWaitForAtom = serverExistsAtom; 359 360 while (!timeOut) { 361 /* NOTE: XtAppNextEvent() does call the timeout routine but 362 ** doesn't return unless there are more events. */ 363 XEvent event; 364 const XPropertyEvent *e = (const XPropertyEvent *)&event; 365 XtAppNextEvent(context, &event); 366 367 /* We will get a PropertyNewValue when the server recreates 368 ** the server exists atom. */ 369 if (e->type == PropertyNotify && 370 e->window == rootWindow && 371 e->atom == serverExistsAtom) { 372 if (e->state == PropertyNewValue) { 373 break; 374 } 375 } 376 XtDispatchEvent(&event); 377 } 378 379 /* Start a new server if the timeout expired. The server exists 380 ** property was not recreated. */ 381 if (timeOut) { 382 serverExists = False; 383 } else { 384 XtRemoveTimeOut(timerId); 385 } 386 } 387 388 return(serverExists); 389 } 390 391 392 393 394 static void startNewServer(XtAppContext context, 395 Window rootWindow, 396 char* commandLine, 397 Atom serverExistsAtom) 398 { 399 Boolean timeOut = False; 400 XtIntervalId timerId; 401 402 /* Add back the server name resource from the command line or resource 403 database to the command line for starting the server. If -svrcmd 404 appeared on the original command line, it was removed by 405 CreatePreferencesDatabase before the command line was recorded 406 in commandLine.shell. Moreover, if no server name was specified, it 407 may have defaulted to the ClearCase view tag. */ 408 if (Preferences.serverName[0] != '\0') { 409 strcat(commandLine, " -svrname "); 410 strcat(commandLine, Preferences.serverName); 411 } 412 switch (startServer("No servers available, start one? (y|n) [y]: ", 413 commandLine)) 414 { 415 case -1: /* Start failed */ 416 XtCloseDisplay(TheDisplay); 417 exit(EXIT_FAILURE); 418 break; 419 case -2: /* Start canceled by user */ 420 XtCloseDisplay(TheDisplay); 421 exit(EXIT_SUCCESS); 422 break; 423 } 424 425 /* Set up a timeout proc in case the server is dead. The standard 426 selection timeout is probably a good guess at how long to wait 427 for this style of inter-client communication as well */ 428 timerId = XtAppAddTimeOut(context, 429 SERVER_START_TIMEOUT, 430 (XtTimerCallbackProc)timeOutProc, 431 &timeOut); 432 currentWaitForAtom = serverExistsAtom; 433 434 /* Wait for the server to start */ 435 while (!timeOut) { 436 XEvent event; 437 const XPropertyEvent *e = (const XPropertyEvent *)&event; 438 /* NOTE: XtAppNextEvent() does call the timeout routine but 439 ** doesn't return unless there are more events. */ 440 XtAppNextEvent(context, &event); 441 442 /* We will get a PropertyNewValue when the server updates 443 ** the server exists atom. If the property is deleted the 444 ** the server must have died. */ 445 if (e->type == PropertyNotify && 446 e->window == rootWindow && 447 e->atom == serverExistsAtom) { 448 if (e->state == PropertyNewValue) { 449 break; 450 } else if (e->state == PropertyDelete) { 451 fprintf(stderr, "%s: The server failed to start.\n", APP_NAME); 452 XtCloseDisplay(TheDisplay); 453 exit(EXIT_FAILURE); 454 } 455 } 456 XtDispatchEvent(&event); 457 } 458 /* Exit if the timeout expired. */ 459 if (timeOut) { 460 fprintf(stderr, "%s: The server failed to start (time-out).\n", APP_NAME); 461 XtCloseDisplay(TheDisplay); 462 exit(EXIT_FAILURE); 463 } else { 464 XtRemoveTimeOut(timerId); 465 } 466 } 467 468 /* 469 ** Prompt the user about starting a server, with "message", then start server 470 */ 471 static int startServer(const char *message, const char *commandLineArgs) 472 { 473 char c, *commandLine; 474 int sysrc; 475 476 /* prompt user whether to start server */ 477 if (!Preferences.autoStart) { 478 printf("%s", message); 479 do { 480 c = getc(stdin); 481 } while (c == ' ' || c == '\t'); 482 if (c != 'Y' && c != 'y' && c != '\n') 483 return (-2); 484 } 485 486 /* start the server */ 487 commandLine = XtMalloc(strlen(Preferences.serverCmd) + 488 strlen(commandLineArgs) + 3); 489 sprintf(commandLine, "%s %s", Preferences.serverCmd, commandLineArgs); 490 491 sysrc=system(commandLine); 492 XtFree(commandLine); 493 494 if (sysrc==0) 495 return 0; 496 else 497 return (-1); 498 } 499 500 /* Reconstruct the command line in string commandLine in case we have to 501 * start a server (nc command line args parallel nedit's). Include 502 * -svrname if nc wants a named server, so nedit will match. Special 503 * characters are protected from the shell by escaping EVERYTHING with \ 504 */ 505 static CommandLine processCommandLine(int argc, char** argv) 506 { 507 CommandLine commandLine; 508 int i; 509 int length = 0; 510 511 for (i=1; i<argc; i++) { 512 length += 1 + strlen(argv[i])*4 + 2; 513 } 514 commandLine.shell = XtMalloc(length+1 + 9 + MAXPATHLEN); 515 *commandLine.shell = '\0'; 516 517 /* Convert command line arguments into a command string for the server */ 518 parseCommandLine(argc, argv, &commandLine); 519 if (commandLine.serverRequest == NULL) { 520 fprintf(stderr, "xnc: Invalid commandline argument\n"); 521 exit(EXIT_FAILURE); 522 } 523 524 return(commandLine); 525 } 526 527 528 /* 529 ** Converts command line into a command string suitable for passing to 530 ** the server 531 */ 532 static void parseCommandLine(int argc, char **argv, CommandLine *commandLine) 533 { 534 #define MAX_RECORD_HEADER_LENGTH 38 535 char name[MAXPATHLEN], path[MAXPATHLEN]; 536 const char *toDoCommand = "", *langMode = "", *geometry = ""; 537 char *commandString, *outPtr; 538 int lineNum = 0, read = 0, create = 0, iconic = 0, tabbed = -1, length = 0; 539 int i, lineArg, nRead, charsWritten, opts = True; 540 int fileCount = 0, group = 0, isTabbed; 541 542 /* Allocate a string for output, for the maximum possible length. The 543 maximum length is calculated by assuming every argument is a file, 544 and a complete record of maximum length is created for it */ 545 for (i=1; i<argc; i++) { 546 length += MAX_RECORD_HEADER_LENGTH + strlen(argv[i]) + MAXPATHLEN; 547 } 548 /* In case of no arguments, must still allocate space for one record header */ 549 if (length < MAX_RECORD_HEADER_LENGTH) 550 { 551 length = MAX_RECORD_HEADER_LENGTH; 552 } 553 commandString = XtMalloc(length+1); 554 555 /* Parse the arguments and write the output string */ 556 outPtr = commandString; 557 for (i=1; i<argc; i++) { 558 if (opts && !strcmp(argv[i], "--")) { 559 opts = False; /* treat all remaining arguments as filenames */ 560 continue; 561 } else if (opts && !strcmp(argv[i], "-do")) { 562 nextArg(argc, argv, &i); 563 toDoCommand = argv[i]; 564 } else if (opts && !strcmp(argv[i], "-lm")) { 565 copyCommandLineArg(commandLine, argv[i]); 566 nextArg(argc, argv, &i); 567 langMode = argv[i]; 568 copyCommandLineArg(commandLine, argv[i]); 569 } else if (opts && (!strcmp(argv[i], "-g") || 570 !strcmp(argv[i], "-geometry"))) { 571 copyCommandLineArg(commandLine, argv[i]); 572 nextArg(argc, argv, &i); 573 geometry = argv[i]; 574 copyCommandLineArg(commandLine, argv[i]); 575 } else if (opts && !strcmp(argv[i], "-read")) { 576 read = 1; 577 } else if (opts && !strcmp(argv[i], "-create")) { 578 create = 1; 579 } else if (opts && !strcmp(argv[i], "-tabbed")) { 580 tabbed = 1; 581 group = 0; /* override -group option */ 582 } else if (opts && !strcmp(argv[i], "-untabbed")) { 583 tabbed = 0; 584 group = 0; /* override -group option */ 585 } else if (opts && !strcmp(argv[i], "-group")) { 586 group = 2; /* 2: start new group, 1: in group */ 587 } else if (opts && (!strcmp(argv[i], "-iconic") || 588 !strcmp(argv[i], "-icon"))) { 589 iconic = 1; 590 copyCommandLineArg(commandLine, argv[i]); 591 } else if (opts && !strcmp(argv[i], "-line")) { 592 nextArg(argc, argv, &i); 593 nRead = sscanf(argv[i], "%d", &lineArg); 594 if (nRead != 1) 595 fprintf(stderr, "xnc: argument to line should be a number\n"); 596 else 597 lineNum = lineArg; 598 } else if (opts && (*argv[i] == '+')) { 599 nRead = sscanf((argv[i]+1), "%d", &lineArg); 600 if (nRead != 1) 601 fprintf(stderr, "xnc: argument to + should be a number\n"); 602 else 603 lineNum = lineArg; 604 } else if (opts && (!strcmp(argv[i], "-ask") || !strcmp(argv[i], "-noask"))) { 605 ; /* Ignore resource-based arguments which are processed later */ 606 } else if (opts && (!strcmp(argv[i], "-svrname") || 607 !strcmp(argv[i], "-svrcmd"))) { 608 nextArg(argc, argv, &i); /* Ignore rsrc args with data */ 609 } else if (opts && (!strcmp(argv[i], "-xrm") || 610 !strcmp(argv[i], "-display"))) { 611 copyCommandLineArg(commandLine, argv[i]); 612 nextArg(argc, argv, &i); /* Ignore rsrc args with data */ 613 copyCommandLineArg(commandLine, argv[i]); 614 } else if (opts && (!strcmp(argv[i], "-version") || !strcmp(argv[i], "-V"))) { 615 printNcVersion(); 616 exit(EXIT_SUCCESS); 617 } else if (opts && (!strcmp(argv[i], "-h") || 618 !strcmp(argv[i], "-help"))) { 619 fprintf(stderr, "%s", cmdLineHelp); 620 exit(EXIT_SUCCESS); 621 } else if (opts && (*argv[i] == '-')) { 622 fprintf(stderr, "xnc: Unrecognized option %s\n%s", argv[i], 623 cmdLineHelp); 624 exit(EXIT_FAILURE); 625 } else { 626 if (ParseFilename(argv[i], name, path) != 0) { 627 /* An Error, most likely too long paths/strings given */ 628 commandLine->serverRequest = NULL; 629 return; 630 } 631 strcat(path, name); 632 633 /* determine if file is to be openned in new tab, by 634 factoring the options -group, -tabbed & -untabbed */ 635 if (group == 2) { 636 isTabbed = 0; /* start a new window for new group */ 637 group = 1; /* next file will be within group */ 638 } 639 else if (group == 1) { 640 isTabbed = 1; /* new tab for file in group */ 641 } 642 else { 643 isTabbed = tabbed; /* not in group */ 644 } 645 646 /* SunOS 4 acc or acc and/or its runtime library has a bug 647 such that %n fails (segv) if it follows a string in a 648 printf or sprintf. The silly code below avoids this. 649 650 The "long" cast on strlen() is necessary because size_t 651 is 64 bit on Alphas, and 32-bit on most others. There is 652 no printf format specifier for "size_t", thanx, ANSI. */ 653 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%n", lineNum, 654 read, create, iconic, isTabbed, (long) strlen(path), 655 (long) strlen(toDoCommand), (long) strlen(langMode), 656 (long) strlen(geometry), &charsWritten); 657 outPtr += charsWritten; 658 strcpy(outPtr, path); 659 outPtr += strlen(path); 660 *outPtr++ = '\n'; 661 strcpy(outPtr, toDoCommand); 662 outPtr += strlen(toDoCommand); 663 *outPtr++ = '\n'; 664 strcpy(outPtr, langMode); 665 outPtr += strlen(langMode); 666 *outPtr++ = '\n'; 667 strcpy(outPtr, geometry); 668 outPtr += strlen(geometry); 669 *outPtr++ = '\n'; 670 671 /* Create the file open atoms for the paths supplied */ 672 addToFileList(path); 673 fileCount++; 674 675 /* These switches only affect the next filename argument, not all */ 676 toDoCommand = ""; 677 lineNum = 0; 678 } 679 } 680 681 /* If there's an un-written -do command, or we are to open a new window, 682 * or user has requested iconic state, but not provided a file name, 683 * create a server request with an empty file name and requested 684 * iconic state (and optional language mode and geometry). 685 */ 686 if (toDoCommand[0] != '\0' || fileCount == 0) { 687 sprintf(outPtr, "0 0 0 %d %d 0 %ld %ld %ld\n\n%n", iconic, tabbed, 688 (long) strlen(toDoCommand), 689 (long) strlen(langMode), (long) strlen(geometry), &charsWritten); 690 outPtr += charsWritten; 691 strcpy(outPtr, toDoCommand); 692 outPtr += strlen(toDoCommand); 693 *outPtr++ = '\n'; 694 strcpy(outPtr, langMode); 695 outPtr += strlen(langMode); 696 *outPtr++ = '\n'; 697 strcpy(outPtr, geometry); 698 outPtr += strlen(geometry); 699 *outPtr++ = '\n'; 700 } 701 702 *outPtr = '\0'; 703 commandLine->serverRequest = commandString; 704 } 705 706 707 static void waitUntilRequestProcessed(XtAppContext context, 708 Window rootWindow, 709 char* commandString, 710 Atom serverRequestAtom) 711 { 712 XtIntervalId timerId; 713 Boolean timeOut = False; 714 715 /* Set the NEDIT_SERVER_REQUEST_<user>_<host> property on the root 716 window to activate the server */ 717 XChangeProperty(TheDisplay, rootWindow, serverRequestAtom, XA_STRING, 8, 718 PropModeReplace, (unsigned char *)commandString, 719 strlen(commandString)); 720 721 /* Set up a timeout proc in case the server is dead. The standard 722 selection timeout is probably a good guess at how long to wait 723 for this style of inter-client communication as well */ 724 timerId = XtAppAddTimeOut(context, 725 REQUEST_TIMEOUT, 726 (XtTimerCallbackProc)timeOutProc, 727 &timeOut); 728 currentWaitForAtom = serverRequestAtom; 729 730 /* Wait for the property to be deleted to know the request was processed */ 731 while (!timeOut) { 732 XEvent event; 733 const XPropertyEvent *e = (const XPropertyEvent *)&event; 734 735 XtAppNextEvent(context, &event); 736 if (e->window == rootWindow && 737 e->atom == serverRequestAtom && 738 e->state == PropertyDelete) 739 break; 740 XtDispatchEvent(&event); 741 } 742 743 /* Exit if the timeout expired. */ 744 if (timeOut) { 745 fprintf(stderr, "%s: The server did not respond to the request.\n", APP_NAME); 746 XtCloseDisplay(TheDisplay); 747 exit(EXIT_FAILURE); 748 } else { 749 XtRemoveTimeOut(timerId); 750 } 751 } 752 753 static void waitUntilFilesOpenedOrClosed(XtAppContext context, 754 Window rootWindow) 755 { 756 XtIntervalId timerId; 757 Boolean timeOut = False; 758 759 /* Set up a timeout proc so we don't wait forever if the server is dead. 760 The standard selection timeout is probably a good guess at how 761 long to wait for this style of inter-client communication as 762 well */ 763 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT, 764 (XtTimerCallbackProc)timeOutProc, &timeOut); 765 currentWaitForAtom = noAtom; 766 767 /* Wait for all of the windows to be opened by server, 768 * and closed if -wait was supplied */ 769 while (fileListHead.fileList) { 770 XEvent event; 771 const XPropertyEvent *e = (const XPropertyEvent *)&event; 772 773 XtAppNextEvent(context, &event); 774 775 /* Update the fileList and check if all files have been closed. */ 776 if (e->type == PropertyNotify && e->window == rootWindow) { 777 FileListEntry *item; 778 779 if (e->state == PropertyDelete) { 780 for (item = fileListHead.fileList; item; item = item->next) { 781 if (e->atom == item->waitForFileOpenAtom) { 782 /* The 'waitForFileOpen' property is deleted when the file is opened */ 783 fileListHead.waitForOpenCount--; 784 item->waitForFileOpenAtom = None; 785 786 /* Reset the timer while we wait for all files to be opened. */ 787 XtRemoveTimeOut(timerId); 788 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT, 789 (XtTimerCallbackProc)timeOutProc, &timeOut); 790 } else if (e->atom == item->waitForFileClosedAtom) { 791 /* When file is opened in -wait mode the property 792 * is deleted when the file is closed. 793 */ 794 fileListHead.waitForCloseCount--; 795 item->waitForFileClosedAtom = None; 796 } 797 } 798 799 if (fileListHead.waitForOpenCount == 0 && !timeOut) { 800 XtRemoveTimeOut(timerId); 801 } 802 803 if (fileListHead.waitForOpenCount == 0 && 804 fileListHead.waitForCloseCount == 0) { 805 break; 806 } 807 } 808 } 809 810 /* We are finished if we are only waiting for files to open and 811 ** the file open timeout has expired. */ 812 if (!Preferences.waitForClose && timeOut) { 813 break; 814 } 815 816 XtDispatchEvent(&event); 817 } 818 } 819 820 821 static void nextArg(int argc, char **argv, int *argIndex) 822 { 823 if (*argIndex + 1 >= argc) { 824 fprintf(stderr, "xnc: %s requires an argument\n%s", 825 argv[*argIndex], cmdLineHelp); 826 exit(EXIT_FAILURE); 827 } 828 (*argIndex)++; 829 } 830 831 /* Copies a given xnc command line argument to the server startup command 832 ** line (-icon, -geometry, -xrm, ...) Special characters are protected from 833 ** the shell by escaping EVERYTHING with \ 834 ** Note that the .shell string in the command line structure is large enough 835 ** to hold the escaped characters. 836 */ 837 static void copyCommandLineArg(CommandLine *commandLine, const char *arg) 838 { 839 const char *c; 840 char *outPtr = commandLine->shell + strlen(commandLine->shell); 841 *outPtr++ = '\''; 842 for (c=arg; *c!='\0'; c++) { 843 if (*c == '\'') { 844 *outPtr++ = '\''; 845 *outPtr++ = '\\'; 846 } 847 *outPtr++ = *c; 848 if (*c == '\'') { 849 *outPtr++ = '\''; 850 } 851 } 852 *outPtr++ = '\''; 853 *outPtr++ = ' '; 854 *outPtr = '\0'; 855 } 856 857 /* Print version of 'xnc' */ 858 static void printNcVersion(void ) { 859 static const char *const xncHelpText = \ 860 "xnc (XNEdit) Version 1.0 (February 2019)\n\n\ 861 Built on: %s, %s, %s\n\ 862 Built at: %s, %s\n"; 863 864 fprintf(stdout, xncHelpText, 865 COMPILE_OS, COMPILE_MACHINE, COMPILE_COMPILER, 866 __DATE__, __TIME__); 867 } 868