Committed SF patch #963120: Open tab in window on current desktop.
[nedit.git] / source / nc.c
blob05ab20b32c5f4733c5f3d9ea144ec54273ae002f
1 static const char CVSID[] = "$Id: nc.c,v 1.39 2004/02/19 06:23:14 tksoh Exp $";
2 /*******************************************************************************
3 * *
4 * nc.c -- Nirvana Editor client program for nedit server processes *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
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 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
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"
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <limits.h>
42 #include <string.h>
43 #ifdef VMS
44 #include <lib$routines.h>
45 #include descrip
46 #include ssdef
47 #include syidef
48 #include "../util/VMSparam.h"
49 #include "../util/VMSutils.h"
50 #else
51 #ifndef __MVS__
52 #include <sys/param.h>
53 #endif
54 #include <sys/types.h>
55 #include <sys/utsname.h>
56 #include <unistd.h>
57 #include <pwd.h>
58 #include "../util/clearcase.h"
59 #endif /* VMS */
60 #ifdef __EMX__
61 #include <process.h>
62 #endif
64 #include <X11/Intrinsic.h>
65 #include <X11/Xatom.h>
67 #ifdef HAVE_DEBUG_H
68 #include "../debug.h"
69 #endif
71 #define APP_NAME "nc"
72 #define APP_CLASS "NEditClient"
74 #define PROPERTY_CHANGE_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */
75 #define SERVER_START_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */
76 #define REQUEST_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */
77 #define FILE_OPEN_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */
79 typedef struct
81 char* shell;
82 char* serverRequest;
83 } CommandLine;
85 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id);
86 static int startServer(const char *message, const char *commandLine);
87 static CommandLine processCommandLine(int argc, char** argv);
88 static void parseCommandLine(int argc, char **arg, CommandLine *cmdLine);
89 static void nextArg(int argc, char **argv, int *argIndex);
90 static void copyCommandLineArg(CommandLine *cmdLine, const char *arg);
91 static void printNcVersion(void);
92 static Boolean findExistingServer(XtAppContext context,
93 Window rootWindow,
94 Atom serverExistsAtom);
95 static void startNewServer(XtAppContext context,
96 Window rootWindow,
97 char* commandLine,
98 Atom serverExistsAtom);
99 static void waitUntilRequestProcessed(XtAppContext context,
100 Window rootWindow,
101 char* commandString,
102 Atom serverRequestAtom);
103 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
104 Window rootWindow);
106 Display *TheDisplay;
107 XtAppContext AppContext;
108 static Atom currentWaitForAtom;
109 static Atom noAtom = (Atom)(-1);
111 static const char cmdLineHelp[] =
112 #ifdef VMS
114 #else
115 "Usage: nc [-read] [-create]\n"
116 " [-line n | +n] [-do command] [-lm languagemode]\n"
117 " [-svrname name] [-svrcmd command]\n"
118 " [-ask] [-noask] [-timeout seconds]\n"
119 " [-geometry geometry | -g geometry] [-icon | -iconic]\n"
120 " [-tabbed] [-untabbed] [-group] [-wait]\n"
121 " [-V | -version]\n"
122 " [-xrm resourcestring] [-display [host]:server[.screen]]\n"
123 " [--] [file...]\n";
124 #endif /*VMS*/
126 /* Structure to hold X Resource values */
127 static struct {
128 int autoStart;
129 char serverCmd[2*MAXPATHLEN]; /* holds executable name + flags */
130 char serverName[MAXPATHLEN];
131 int waitForClose;
132 int timeOut;
133 } Preferences;
135 /* Application resources */
136 static PrefDescripRec PrefDescrip[] = {
137 {"autoStart", "AutoStart", PREF_BOOLEAN, "True",
138 &Preferences.autoStart, NULL, True},
139 {"serverCommand", "ServerCommand", PREF_STRING, "nedit -server",
140 Preferences.serverCmd, (void *)sizeof(Preferences.serverCmd), False},
141 {"serverName", "serverName", PREF_STRING, "", Preferences.serverName,
142 (void *)sizeof(Preferences.serverName), False},
143 {"waitForClose", "WaitForClose", PREF_BOOLEAN, "False",
144 &Preferences.waitForClose, NULL, False},
145 {"timeOut", "TimeOut", PREF_INT, "10",
146 &Preferences.timeOut, NULL, False}
149 /* Resource related command line options */
150 static XrmOptionDescRec OpTable[] = {
151 {"-ask", ".autoStart", XrmoptionNoArg, (caddr_t)"False"},
152 {"-noask", ".autoStart", XrmoptionNoArg, (caddr_t)"True"},
153 {"-svrname", ".serverName", XrmoptionSepArg, (caddr_t)NULL},
154 {"-svrcmd", ".serverCommand", XrmoptionSepArg, (caddr_t)NULL},
155 {"-wait", ".waitForClose", XrmoptionNoArg, (caddr_t)"True"},
156 {"-timeout", ".timeOut", XrmoptionSepArg, (caddr_t)NULL}
159 /* Struct to hold info about files being opened and edited. */
160 typedef struct _FileListEntry {
161 Atom waitForFileOpenAtom;
162 Atom waitForFileClosedAtom;
163 char* path;
164 struct _FileListEntry *next;
165 } FileListEntry;
167 typedef struct {
168 int waitForOpenCount;
169 int waitForCloseCount;
170 FileListEntry* fileList;
171 } FileListHead;
172 static FileListHead fileListHead;
174 static void setPropertyValue(Atom atom) {
175 XChangeProperty(TheDisplay,
176 RootWindow(TheDisplay, DefaultScreen(TheDisplay)),
177 atom, XA_STRING,
178 8, PropModeReplace,
179 (unsigned char *)"True", 4);
182 /* Add another entry to the file entry list, if it doesn't exist yet. */
183 static void addToFileList(const char *path)
185 FileListEntry *item;
187 /* see if the file already exists in the list */
188 for (item = fileListHead.fileList; item; item = item->next) {
189 if (!strcmp(item->path, path))
190 break;
193 /* Add the atom to the head of the file list if it wasn't found. */
194 if (item == 0) {
195 item = malloc(sizeof(item[0]));
196 item->waitForFileOpenAtom = None;
197 item->waitForFileClosedAtom = None;
198 item->path = (char*)malloc(strlen(path)+1);
199 strcpy(item->path, path);
200 item->next = fileListHead.fileList;
201 fileListHead.fileList = item;
205 /* Creates the properties for the various paths */
206 static void createWaitProperties()
208 FileListEntry *item;
210 for (item = fileListHead.fileList; item; item = item->next) {
211 fileListHead.waitForOpenCount++;
212 item->waitForFileOpenAtom =
213 CreateServerFileOpenAtom(Preferences.serverName, item->path);
214 setPropertyValue(item->waitForFileOpenAtom);
216 if (Preferences.waitForClose == True) {
217 fileListHead.waitForCloseCount++;
218 item->waitForFileClosedAtom =
219 CreateServerFileClosedAtom(Preferences.serverName,
220 item->path,
221 False);
222 setPropertyValue(item->waitForFileClosedAtom);
227 int main(int argc, char **argv)
229 XtAppContext context;
230 Window rootWindow;
231 CommandLine commandLine;
232 Atom serverExistsAtom, serverRequestAtom;
233 XrmDatabase prefDB;
234 Boolean serverExists;
236 /* Initialize toolkit and get an application context */
237 XtToolkitInitialize();
238 AppContext = context = XtCreateApplicationContext();
240 #ifdef VMS
241 /* Convert the command line to Unix style */
242 ConvertVMSCommandLine(&argc, &argv);
243 #endif /*VMS*/
244 #ifdef __EMX__
245 /* expand wildcards if necessary */
246 _wildcard(&argc, &argv);
247 #endif
249 /* Read the preferences command line into a database (note that we
250 don't support the .nc file anymore) */
251 prefDB = CreatePreferencesDatabase(NULL, APP_CLASS,
252 OpTable, XtNumber(OpTable), (unsigned *)&argc, argv);
254 /* Process the command line before calling XtOpenDisplay, because the
255 latter consumes certain command line arguments that we still need
256 (-icon, -geometry ...) */
257 commandLine = processCommandLine(argc, argv);
259 /* Open the display and find the root window */
260 TheDisplay = XtOpenDisplay (context, NULL, APP_NAME, APP_CLASS, NULL,
261 0, &argc, argv);
262 if (!TheDisplay) {
263 XtWarning ("nc: Can't open display\n");
264 exit(EXIT_FAILURE);
266 rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
268 /* Read the application resources into the Preferences data structure */
269 RestorePreferences(prefDB, XtDatabase(TheDisplay), APP_NAME,
270 APP_CLASS, PrefDescrip, XtNumber(PrefDescrip));
272 /* Make sure that the time out unit is at least 1 second and not too
273 large either (overflow!). */
274 if (Preferences.timeOut < 1) {
275 Preferences.timeOut = 1;
276 } else if (Preferences.timeOut > 1000) {
277 Preferences.timeOut = 1000;
280 #ifndef VMS
281 /* For Clearcase users who have not set a server name, use the clearcase
282 view name. Clearcase views make files with the same absolute path names
283 but different contents (and therefore can't be edited in the same nedit
284 session). This should have no bad side-effects for non-clearcase users */
285 if (Preferences.serverName[0] == '\0') {
286 const char* viewTag = GetClearCaseViewTag();
287 if (viewTag != NULL && strlen(viewTag) < MAXPATHLEN) {
288 strcpy(Preferences.serverName, viewTag);
291 #endif /* VMS */
293 /* Create the wait properties for the various files. */
294 createWaitProperties();
296 /* Monitor the properties on the root window */
297 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask);
299 /* Create the server property atoms on the current DISPLAY. */
300 CreateServerPropertyAtoms(Preferences.serverName,
301 &serverExistsAtom,
302 &serverRequestAtom);
304 serverExists = findExistingServer(context,
305 rootWindow,
306 serverExistsAtom);
308 if (serverExists == False)
309 startNewServer(context, rootWindow, commandLine.shell, serverExistsAtom);
311 waitUntilRequestProcessed(context,
312 rootWindow,
313 commandLine.serverRequest,
314 serverRequestAtom);
316 waitUntilFilesOpenedOrClosed(context, rootWindow);
318 XtCloseDisplay(TheDisplay);
319 XtFree(commandLine.shell);
320 XtFree(commandLine.serverRequest);
321 return 0;
326 ** Xt timer procedure for timeouts on NEdit server requests
328 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id)
330 /* NOTE: XtAppNextEvent() does call this routine but
331 ** doesn't return unless there are more events.
332 ** Hence, we generate this (synthetic) event to break the deadlock
334 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
335 if (currentWaitForAtom != noAtom) {
336 XChangeProperty(TheDisplay, rootWindow, currentWaitForAtom, XA_STRING,
337 8, PropModeReplace, (unsigned char *)"", strlen(""));
340 /* Flag that the timeout has occurred. */
341 *timeOutReturn = True;
346 static Boolean findExistingServer(XtAppContext context,
347 Window rootWindow,
348 Atom serverExistsAtom)
350 Boolean serverExists = True;
351 unsigned char *propValue;
352 int getFmt;
353 Atom dummyAtom;
354 unsigned long dummyULong, nItems;
356 /* See if there might be a server (not a guaranty), by translating the
357 root window property NEDIT_SERVER_EXISTS_<user>_<host> */
358 if (XGetWindowProperty(TheDisplay, rootWindow, serverExistsAtom, 0,
359 INT_MAX, False, XA_STRING, &dummyAtom, &getFmt, &nItems,
360 &dummyULong, &propValue) != Success || nItems == 0) {
361 serverExists = False;
362 } else {
363 Boolean timeOut = False;
364 XtIntervalId timerId;
366 XFree(propValue);
368 /* Remove the server exists property to make sure the server is
369 ** running. If it is running it will get recreated.
371 XDeleteProperty(TheDisplay, rootWindow, serverExistsAtom);
372 XSync(TheDisplay, False);
373 timerId = XtAppAddTimeOut(context,
374 PROPERTY_CHANGE_TIMEOUT,
375 (XtTimerCallbackProc)timeOutProc,
376 &timeOut);
377 currentWaitForAtom = serverExistsAtom;
379 while (!timeOut) {
380 /* NOTE: XtAppNextEvent() does call the timeout routine but
381 ** doesn't return unless there are more events. */
382 XEvent event;
383 const XPropertyEvent *e = (const XPropertyEvent *)&event;
384 XtAppNextEvent(context, &event);
386 /* We will get a PropertyNewValue when the server recreates
387 ** the server exists atom. */
388 if (e->type == PropertyNotify &&
389 e->window == rootWindow &&
390 e->atom == serverExistsAtom) {
391 if (e->state == PropertyNewValue) {
392 break;
395 XtDispatchEvent(&event);
398 /* Start a new server if the timeout expired. The server exists
399 ** property was not recreated. */
400 if (timeOut) {
401 serverExists = False;
402 } else {
403 XtRemoveTimeOut(timerId);
407 return(serverExists);
413 static void startNewServer(XtAppContext context,
414 Window rootWindow,
415 char* commandLine,
416 Atom serverExistsAtom)
418 Boolean timeOut = False;
419 XtIntervalId timerId;
421 /* Add back the server name resource from the command line or resource
422 database to the command line for starting the server. If -svrcmd
423 appeared on the original command line, it was removed by
424 CreatePreferencesDatabase before the command line was recorded
425 in commandLine.shell. Moreover, if no server name was specified, it
426 may have defaulted to the ClearCase view tag. */
427 if (Preferences.serverName[0] != '\0') {
428 strcat(commandLine, " -svrname ");
429 strcat(commandLine, Preferences.serverName);
431 switch (startServer("No servers available, start one? (y|n) [y]: ",
432 commandLine))
434 case -1: /* Start failed */
435 XtCloseDisplay(TheDisplay);
436 exit(EXIT_FAILURE);
437 break;
438 case -2: /* Start canceled by user */
439 XtCloseDisplay(TheDisplay);
440 exit(EXIT_SUCCESS);
441 break;
444 /* Set up a timeout proc in case the server is dead. The standard
445 selection timeout is probably a good guess at how long to wait
446 for this style of inter-client communication as well */
447 timerId = XtAppAddTimeOut(context,
448 SERVER_START_TIMEOUT,
449 (XtTimerCallbackProc)timeOutProc,
450 &timeOut);
451 currentWaitForAtom = serverExistsAtom;
453 /* Wait for the server to start */
454 while (!timeOut) {
455 XEvent event;
456 const XPropertyEvent *e = (const XPropertyEvent *)&event;
457 /* NOTE: XtAppNextEvent() does call the timeout routine but
458 ** doesn't return unless there are more events. */
459 XtAppNextEvent(context, &event);
461 /* We will get a PropertyNewValue when the server updates
462 ** the server exists atom. If the property is deleted the
463 ** the server must have died. */
464 if (e->type == PropertyNotify &&
465 e->window == rootWindow &&
466 e->atom == serverExistsAtom) {
467 if (e->state == PropertyNewValue) {
468 break;
469 } else if (e->state == PropertyDelete) {
470 fprintf(stderr, "%s: The server failed to start.\n", APP_NAME);
471 XtCloseDisplay(TheDisplay);
472 exit(EXIT_FAILURE);
475 XtDispatchEvent(&event);
477 /* Exit if the timeout expired. */
478 if (timeOut) {
479 fprintf(stderr, "%s: The server failed to start (time-out).\n", APP_NAME);
480 XtCloseDisplay(TheDisplay);
481 exit(EXIT_FAILURE);
482 } else {
483 XtRemoveTimeOut(timerId);
488 ** Prompt the user about starting a server, with "message", then start server
490 static int startServer(const char *message, const char *commandLineArgs)
492 char c, *commandLine;
493 #ifdef VMS
494 int spawnFlags = 1 /* + 1<<3 */; /* NOWAIT, NOKEYPAD */
495 int spawn_sts;
496 struct dsc$descriptor_s *cmdDesc;
497 char *nulDev = "NL:";
498 struct dsc$descriptor_s *nulDevDesc;
499 #else
500 int sysrc;
501 #endif /* !VMS */
503 /* prompt user whether to start server */
504 if (!Preferences.autoStart) {
505 puts(message);
506 do {
507 c = getc(stdin);
508 } while (c == ' ' || c == '\t');
509 if (c != 'Y' && c != 'y' && c != '\n')
510 return (-2);
513 /* start the server */
514 #ifdef VMS
515 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
516 strlen(commandLineArgs) + 3);
517 sprintf(commandLine, "%s %s", Preferences.serverCmd, commandLineArgs);
518 cmdDesc = NulStrToDesc(commandLine); /* build command descriptor */
519 nulDevDesc = NulStrToDesc(nulDev); /* build "NL:" descriptor */
520 spawn_sts = lib$spawn(cmdDesc, nulDevDesc, 0, &spawnFlags, 0,0,0,0,0,0,0,0);
521 XtFree(commandLine);
522 if (spawn_sts != SS$_NORMAL) {
523 fprintf(stderr, "Error return from lib$spawn: %d\n", spawn_sts);
524 fprintf(stderr, "Nedit server not started.\n");
525 return (-1);
526 } else {
527 FreeStrDesc(cmdDesc);
528 return 0;
530 #else
531 #if defined(__EMX__) /* OS/2 */
532 /* Unfortunately system() calls a shell determined by the environment
533 variables COMSPEC and EMXSHELL. We have to figure out which one. */
535 char *sh_spec, *sh, *base;
536 char *CMD_EXE="cmd.exe";
538 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
539 strlen(commandLineArgs) + 15);
540 sh = getenv ("EMXSHELL");
541 if (sh == NULL)
542 sh = getenv ("COMSPEC");
543 if (sh == NULL)
544 sh = CMD_EXE;
545 sh_spec=XtNewString(sh);
546 base=_getname(sh_spec);
547 _remext(base);
548 if (stricmp (base, "cmd") == 0 || stricmp (base, "4os2") == 0) {
549 sprintf(commandLine, "start /C /MIN %s %s",
550 Preferences.serverCmd, commandLineArgs);
551 } else {
552 sprintf(commandLine, "%s %s &",
553 Preferences.serverCmd, commandLineArgs);
555 XtFree(sh_spec);
557 #else /* Unix */
558 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
559 strlen(commandLineArgs) + 3);
560 sprintf(commandLine, "%s %s&", Preferences.serverCmd, commandLineArgs);
561 #endif
563 sysrc=system(commandLine);
564 XtFree(commandLine);
566 if (sysrc==0)
567 return 0;
568 else
569 return (-1);
570 #endif /* !VMS */
573 /* Reconstruct the command line in string commandLine in case we have to
574 * start a server (nc command line args parallel nedit's). Include
575 * -svrname if nc wants a named server, so nedit will match. Special
576 * characters are protected from the shell by escaping EVERYTHING with \
578 static CommandLine processCommandLine(int argc, char** argv)
580 CommandLine commandLine;
581 int i;
582 int length = 0;
584 for (i=1; i<argc; i++) {
585 length += 1 + strlen(argv[i])*4 + 2;
587 commandLine.shell = XtMalloc(length+1 + 9 + MAXPATHLEN);
588 *commandLine.shell = '\0';
590 /* Convert command line arguments into a command string for the server */
591 parseCommandLine(argc, argv, &commandLine);
592 if (commandLine.serverRequest == NULL) {
593 fprintf(stderr, "nc: Invalid commandline argument\n");
594 exit(EXIT_FAILURE);
597 return(commandLine);
602 ** Converts command line into a command string suitable for passing to
603 ** the server
605 static void parseCommandLine(int argc, char **argv, CommandLine *commandLine)
607 #define MAX_RECORD_HEADER_LENGTH 38
608 char name[MAXPATHLEN], path[MAXPATHLEN];
609 const char *toDoCommand = "", *langMode = "", *geometry = "";
610 char *commandString, *outPtr;
611 int lineNum = 0, read = 0, create = 0, iconic = 0, tabbed = -1, length = 0;
612 int i, lineArg, nRead, charsWritten, opts = True;
613 int fileCount = 0, group = 0, isTabbed;
615 /* Allocate a string for output, for the maximum possible length. The
616 maximum length is calculated by assuming every argument is a file,
617 and a complete record of maximum length is created for it */
618 for (i=1; i<argc; i++) {
619 length += MAX_RECORD_HEADER_LENGTH + strlen(argv[i]) + MAXPATHLEN;
621 /* In case of no arguments, must still allocate space for one record header */
622 if (length < MAX_RECORD_HEADER_LENGTH)
624 length = MAX_RECORD_HEADER_LENGTH;
626 commandString = XtMalloc(length+1);
628 /* Parse the arguments and write the output string */
629 outPtr = commandString;
630 for (i=1; i<argc; i++) {
631 if (opts && !strcmp(argv[i], "--")) {
632 opts = False; /* treat all remaining arguments as filenames */
633 continue;
634 } else if (opts && !strcmp(argv[i], "-do")) {
635 nextArg(argc, argv, &i);
636 toDoCommand = argv[i];
637 } else if (opts && !strcmp(argv[i], "-lm")) {
638 copyCommandLineArg(commandLine, argv[i]);
639 nextArg(argc, argv, &i);
640 langMode = argv[i];
641 copyCommandLineArg(commandLine, argv[i]);
642 } else if (opts && (!strcmp(argv[i], "-g") ||
643 !strcmp(argv[i], "-geometry"))) {
644 copyCommandLineArg(commandLine, argv[i]);
645 nextArg(argc, argv, &i);
646 geometry = argv[i];
647 copyCommandLineArg(commandLine, argv[i]);
648 } else if (opts && !strcmp(argv[i], "-read")) {
649 read = 1;
650 } else if (opts && !strcmp(argv[i], "-create")) {
651 create = 1;
652 } else if (opts && !strcmp(argv[i], "-tabbed")) {
653 tabbed = 1;
654 group = 0; /* override -group option */
655 } else if (opts && !strcmp(argv[i], "-untabbed")) {
656 tabbed = 0;
657 group = 0; /* override -group option */
658 } else if (opts && !strcmp(argv[i], "-group")) {
659 group = 2; /* 2: start new group, 1: in group */
660 } else if (opts && (!strcmp(argv[i], "-iconic") ||
661 !strcmp(argv[i], "-icon"))) {
662 iconic = 1;
663 copyCommandLineArg(commandLine, argv[i]);
664 } else if (opts && !strcmp(argv[i], "-line")) {
665 nextArg(argc, argv, &i);
666 nRead = sscanf(argv[i], "%d", &lineArg);
667 if (nRead != 1)
668 fprintf(stderr, "nc: argument to line should be a number\n");
669 else
670 lineNum = lineArg;
671 } else if (opts && (*argv[i] == '+')) {
672 nRead = sscanf((argv[i]+1), "%d", &lineArg);
673 if (nRead != 1)
674 fprintf(stderr, "nc: argument to + should be a number\n");
675 else
676 lineNum = lineArg;
677 } else if (opts && (!strcmp(argv[i], "-ask") || !strcmp(argv[i], "-noask"))) {
678 ; /* Ignore resource-based arguments which are processed later */
679 } else if (opts && (!strcmp(argv[i], "-svrname") ||
680 !strcmp(argv[i], "-svrcmd"))) {
681 nextArg(argc, argv, &i); /* Ignore rsrc args with data */
682 } else if (opts && (!strcmp(argv[i], "-xrm") ||
683 !strcmp(argv[i], "-display"))) {
684 copyCommandLineArg(commandLine, argv[i]);
685 nextArg(argc, argv, &i); /* Ignore rsrc args with data */
686 copyCommandLineArg(commandLine, argv[i]);
687 } else if (opts && (!strcmp(argv[i], "-version") || !strcmp(argv[i], "-V"))) {
688 printNcVersion();
689 exit(EXIT_SUCCESS);
690 } else if (opts && (*argv[i] == '-')) {
691 #ifdef VMS
692 *argv[i] = '/';
693 #endif /*VMS*/
694 fprintf(stderr, "nc: Unrecognized option %s\n%s", argv[i],
695 cmdLineHelp);
696 exit(EXIT_FAILURE);
697 } else {
698 #ifdef VMS
699 int numFiles, j, oldLength;
700 char **nameList = NULL, *newCommandString;
701 /* Use VMS's LIB$FILESCAN for filename in argv[i] to process */
702 /* wildcards and to obtain a full VMS file specification */
703 numFiles = VMSFileScan(argv[i], &nameList, NULL, INCLUDE_FNF);
704 /* for each expanded file name do: */
705 for (j = 0; j < numFiles; ++j) {
706 oldLength = outPtr-commandString;
707 newCommandString = XtMalloc(oldLength+length+1);
708 strncpy(newCommandString, commandString, oldLength);
709 XtFree(commandString);
710 commandString = newCommandString;
711 outPtr = newCommandString + oldLength;
712 if (ParseFilename(nameList[j], name, path) != 0) {
713 /* An Error, most likely too long paths/strings given */
714 commandLine->serverRequest = NULL;
715 return;
717 strcat(path, name);
719 /* determine if file is to be openned in new tab, by
720 factoring the options -group, -tabbed & -untabbed */
721 if (group == 2) {
722 isTabbed = 0; /* start a new window for new group */
723 group = 1; /* next file will be within group */
725 else if (group == 1) {
726 isTabbed = 1; /* new tab for file in group */
728 else {
729 isTabbed = tabbed; /* not in group */
732 /* See below for casts */
733 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%s\n%s\n%s\n%s\n%n",
734 lineNum, read, create, iconic, tabbed, (long) strlen(path),
735 (long) strlen(toDoCommand), (long) strlen(langMode),
736 (long) strlen(geometry),
737 path, toDoCommand, langMode, geometry, &charsWritten);
738 outPtr += charsWritten;
739 free(nameList[j]);
741 /* Create the file open atoms for the paths supplied */
742 addToFileList(path);
743 fileCount++;
745 if (nameList != NULL)
746 free(nameList);
747 #else
748 if (ParseFilename(argv[i], name, path) != 0) {
749 /* An Error, most likely too long paths/strings given */
750 commandLine->serverRequest = NULL;
751 return;
753 strcat(path, name);
755 /* determine if file is to be openned in new tab, by
756 factoring the options -group, -tabbed & -untabbed */
757 if (group == 2) {
758 isTabbed = 0; /* start a new window for new group */
759 group = 1; /* next file will be within group */
761 else if (group == 1) {
762 isTabbed = 1; /* new tab for file in group */
764 else {
765 isTabbed = tabbed; /* not in group */
768 /* SunOS 4 acc or acc and/or its runtime library has a bug
769 such that %n fails (segv) if it follows a string in a
770 printf or sprintf. The silly code below avoids this.
772 The "long" cast on strlen() is necessary because size_t
773 is 64 bit on Alphas, and 32-bit on most others. There is
774 no printf format specifier for "size_t", thanx, ANSI. */
775 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%n", lineNum,
776 read, create, iconic, isTabbed, (long) strlen(path),
777 (long) strlen(toDoCommand), (long) strlen(langMode),
778 (long) strlen(geometry), &charsWritten);
779 outPtr += charsWritten;
780 strcpy(outPtr, path);
781 outPtr += strlen(path);
782 *outPtr++ = '\n';
783 strcpy(outPtr, toDoCommand);
784 outPtr += strlen(toDoCommand);
785 *outPtr++ = '\n';
786 strcpy(outPtr, langMode);
787 outPtr += strlen(langMode);
788 *outPtr++ = '\n';
789 strcpy(outPtr, geometry);
790 outPtr += strlen(geometry);
791 *outPtr++ = '\n';
792 toDoCommand = "";
794 /* Create the file open atoms for the paths supplied */
795 addToFileList(path);
796 fileCount++;
797 #endif /* VMS */
800 #ifdef VMS
801 VMSFileScanDone();
802 #endif /*VMS*/
804 /* If there's an un-written -do command, or we are to open a new window,
805 * or user has requested iconic state, but not provided a file name,
806 * create a server request with an empty file name and requested
807 * iconic state (and optional language mode and geometry).
809 if (toDoCommand[0] != '\0' || fileCount == 0) {
810 sprintf(outPtr, "0 0 0 %d %d 0 %ld %ld %ld\n\n%n", iconic, tabbed,
811 (long) strlen(toDoCommand),
812 (long) strlen(langMode), (long) strlen(geometry), &charsWritten);
813 outPtr += charsWritten;
814 strcpy(outPtr, toDoCommand);
815 outPtr += strlen(toDoCommand);
816 *outPtr++ = '\n';
817 strcpy(outPtr, langMode);
818 outPtr += strlen(langMode);
819 *outPtr++ = '\n';
820 strcpy(outPtr, geometry);
821 outPtr += strlen(geometry);
822 *outPtr++ = '\n';
825 *outPtr = '\0';
826 commandLine->serverRequest = commandString;
830 static void waitUntilRequestProcessed(XtAppContext context,
831 Window rootWindow,
832 char* commandString,
833 Atom serverRequestAtom)
835 XtIntervalId timerId;
836 Boolean timeOut = False;
838 /* Set the NEDIT_SERVER_REQUEST_<user>_<host> property on the root
839 window to activate the server */
840 XChangeProperty(TheDisplay, rootWindow, serverRequestAtom, XA_STRING, 8,
841 PropModeReplace, (unsigned char *)commandString,
842 strlen(commandString));
844 /* Set up a timeout proc in case the server is dead. The standard
845 selection timeout is probably a good guess at how long to wait
846 for this style of inter-client communication as well */
847 timerId = XtAppAddTimeOut(context,
848 REQUEST_TIMEOUT,
849 (XtTimerCallbackProc)timeOutProc,
850 &timeOut);
851 currentWaitForAtom = serverRequestAtom;
853 /* Wait for the property to be deleted to know the request was processed */
854 while (!timeOut) {
855 XEvent event;
856 const XPropertyEvent *e = (const XPropertyEvent *)&event;
858 XtAppNextEvent(context, &event);
859 if (e->window == rootWindow &&
860 e->atom == serverRequestAtom &&
861 e->state == PropertyDelete)
862 break;
863 XtDispatchEvent(&event);
866 /* Exit if the timeout expired. */
867 if (timeOut) {
868 fprintf(stderr, "%s: The server did not respond to the request.\n", APP_NAME);
869 XtCloseDisplay(TheDisplay);
870 exit(EXIT_FAILURE);
871 } else {
872 XtRemoveTimeOut(timerId);
876 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
877 Window rootWindow)
879 XtIntervalId timerId;
880 Boolean timeOut = False;
882 /* Set up a timeout proc so we don't wait forever if the server is dead.
883 The standard selection timeout is probably a good guess at how
884 long to wait for this style of inter-client communication as
885 well */
886 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
887 (XtTimerCallbackProc)timeOutProc, &timeOut);
888 currentWaitForAtom = noAtom;
890 /* Wait for all of the windows to be opened by server,
891 * and closed if -wait was supplied */
892 while (fileListHead.fileList) {
893 XEvent event;
894 const XPropertyEvent *e = (const XPropertyEvent *)&event;
896 XtAppNextEvent(context, &event);
898 /* Update the fileList and check if all files have been closed. */
899 if (e->type == PropertyNotify && e->window == rootWindow) {
900 FileListEntry *item;
902 if (e->state == PropertyDelete) {
903 for (item = fileListHead.fileList; item; item = item->next) {
904 if (e->atom == item->waitForFileOpenAtom) {
905 /* The 'waitForFileOpen' property is deleted when the file is opened */
906 fileListHead.waitForOpenCount--;
907 item->waitForFileOpenAtom = None;
909 /* Reset the timer while we wait for all files to be opened. */
910 XtRemoveTimeOut(timerId);
911 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
912 (XtTimerCallbackProc)timeOutProc, &timeOut);
913 } else if (e->atom == item->waitForFileClosedAtom) {
914 /* When file is opened in -wait mode the property
915 * is deleted when the file is closed.
917 fileListHead.waitForCloseCount--;
918 item->waitForFileClosedAtom = None;
922 if (fileListHead.waitForOpenCount == 0 && !timeOut) {
923 XtRemoveTimeOut(timerId);
926 if (fileListHead.waitForOpenCount == 0 &&
927 fileListHead.waitForCloseCount == 0) {
928 break;
933 /* We are finished if we are only waiting for files to open and
934 ** the file open timeout has expired. */
935 if (!Preferences.waitForClose && timeOut) {
936 break;
939 XtDispatchEvent(&event);
944 static void nextArg(int argc, char **argv, int *argIndex)
946 if (*argIndex + 1 >= argc) {
947 #ifdef VMS
948 *argv[*argIndex] = '/';
949 #endif /*VMS*/
950 fprintf(stderr, "nc: %s requires an argument\n%s",
951 argv[*argIndex], cmdLineHelp);
952 exit(EXIT_FAILURE);
954 (*argIndex)++;
957 /* Copies a given nc command line argument to the server startup command
958 ** line (-icon, -geometry, -xrm, ...) Special characters are protected from
959 ** the shell by escaping EVERYTHING with \
960 ** Note that the .shell string in the command line structure is large enough
961 ** to hold the escaped characters.
963 static void copyCommandLineArg(CommandLine *commandLine, const char *arg)
965 const char *c;
966 char *outPtr = commandLine->shell + strlen(commandLine->shell);
967 #if defined(VMS) || defined(__EMX__)
968 /* Non-Unix shells don't want/need esc */
969 for (c=arg; *c!='\0'; c++) {
970 *outPtr++ = *c;
972 *outPtr++ = ' ';
973 *outPtr = '\0';
974 #else
975 *outPtr++ = '\'';
976 for (c=arg; *c!='\0'; c++) {
977 if (*c == '\'') {
978 *outPtr++ = '\'';
979 *outPtr++ = '\\';
981 *outPtr++ = *c;
982 if (*c == '\'') {
983 *outPtr++ = '\'';
986 *outPtr++ = '\'';
987 *outPtr++ = ' ';
988 *outPtr = '\0';
989 #endif /* VMS */
992 /* Print version of 'nc' */
993 static void printNcVersion(void ) {
994 static const char *const ncHelpText = \
995 "nc (NEdit) Version 5.4 (November 2003)\n\n\
996 Built on: %s, %s, %s\n\
997 Built at: %s, %s\n";
999 fprintf(stdout, ncHelpText,
1000 COMPILE_OS, COMPILE_MACHINE, COMPILE_COMPILER,
1001 __DATE__, __TIME__);