From Ivan Skytte Jørgensen: remove duplicate declarations
[nedit.git] / source / nc.c
blobce29c8654475dfa8b7084b1431d4f0f314ca004b
1 static const char CVSID[] = "$Id: nc.c,v 1.47 2005/08/06 04:31:25 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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * November, 1995 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "server_common.h"
35 #include "../util/fileUtils.h"
36 #include "../util/utils.h"
37 #include "../util/prefFile.h"
38 #include "../util/system.h"
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <limits.h>
43 #include <string.h>
44 #ifdef VMS
45 #include <lib$routines.h>
46 #include descrip
47 #include ssdef
48 #include syidef
49 #include "../util/VMSparam.h"
50 #include "../util/VMSutils.h"
51 #else
52 #ifndef __MVS__
53 #include <sys/param.h>
54 #endif
55 #include <sys/types.h>
56 #include <sys/utsname.h>
57 #include <unistd.h>
58 #include <pwd.h>
59 #include "../util/clearcase.h"
60 #endif /* VMS */
61 #ifdef __EMX__
62 #include <process.h>
63 #endif
65 #include <X11/Intrinsic.h>
66 #include <X11/Xatom.h>
68 #ifdef HAVE_DEBUG_H
69 #include "../debug.h"
70 #endif
72 #define APP_NAME "nc"
73 #define APP_CLASS "NEditClient"
75 #define PROPERTY_CHANGE_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */
76 #define SERVER_START_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */
77 #define REQUEST_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */
78 #define FILE_OPEN_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */
80 typedef struct
82 char* shell;
83 char* serverRequest;
84 } CommandLine;
86 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id);
87 static int startServer(const char *message, const char *commandLine);
88 static CommandLine processCommandLine(int argc, char** argv);
89 static void parseCommandLine(int argc, char **arg, CommandLine *cmdLine);
90 static void nextArg(int argc, char **argv, int *argIndex);
91 static void copyCommandLineArg(CommandLine *cmdLine, const char *arg);
92 static void printNcVersion(void);
93 static Boolean findExistingServer(XtAppContext context,
94 Window rootWindow,
95 Atom serverExistsAtom);
96 static void startNewServer(XtAppContext context,
97 Window rootWindow,
98 char* commandLine,
99 Atom serverExistsAtom);
100 static void waitUntilRequestProcessed(XtAppContext context,
101 Window rootWindow,
102 char* commandString,
103 Atom serverRequestAtom);
104 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
105 Window rootWindow);
107 Display *TheDisplay;
108 XtAppContext AppContext;
109 static Atom currentWaitForAtom;
110 static Atom noAtom = (Atom)(-1);
112 static const char cmdLineHelp[] =
113 #ifdef VMS
114 "[Sorry, no on-line help available.]\n"; /* Why is that ? */
115 #else
116 "Usage: nc [-read] [-create]\n"
117 " [-line n | +n] [-do command] [-lm languagemode]\n"
118 " [-svrname name] [-svrcmd command]\n"
119 " [-ask] [-noask] [-timeout seconds]\n"
120 " [-geometry geometry | -g geometry] [-icon | -iconic]\n"
121 " [-tabbed] [-untabbed] [-group] [-wait]\n"
122 " [-V | -version] [-h|-help]\n"
123 " [-xrm resourcestring] [-display [host]:server[.screen]]\n"
124 " [--] [file...]\n";
125 #endif /*VMS*/
127 /* Structure to hold X Resource values */
128 static struct {
129 int autoStart;
130 char serverCmd[2*MAXPATHLEN]; /* holds executable name + flags */
131 char serverName[MAXPATHLEN];
132 int waitForClose;
133 int timeOut;
134 } Preferences;
136 /* Application resources */
137 static PrefDescripRec PrefDescrip[] = {
138 {"autoStart", "AutoStart", PREF_BOOLEAN, "True",
139 &Preferences.autoStart, NULL, True},
140 {"serverCommand", "ServerCommand", PREF_STRING, "nedit -server",
141 Preferences.serverCmd, (void *)sizeof(Preferences.serverCmd), False},
142 {"serverName", "serverName", PREF_STRING, "", Preferences.serverName,
143 (void *)sizeof(Preferences.serverName), False},
144 {"waitForClose", "WaitForClose", PREF_BOOLEAN, "False",
145 &Preferences.waitForClose, NULL, False},
146 {"timeOut", "TimeOut", PREF_INT, "10",
147 &Preferences.timeOut, NULL, False}
150 /* Resource related command line options */
151 static XrmOptionDescRec OpTable[] = {
152 {"-ask", ".autoStart", XrmoptionNoArg, (caddr_t)"False"},
153 {"-noask", ".autoStart", XrmoptionNoArg, (caddr_t)"True"},
154 {"-svrname", ".serverName", XrmoptionSepArg, (caddr_t)NULL},
155 {"-svrcmd", ".serverCommand", XrmoptionSepArg, (caddr_t)NULL},
156 {"-wait", ".waitForClose", XrmoptionNoArg, (caddr_t)"True"},
157 {"-timeout", ".timeOut", XrmoptionSepArg, (caddr_t)NULL}
160 /* Struct to hold info about files being opened and edited. */
161 typedef struct _FileListEntry {
162 Atom waitForFileOpenAtom;
163 Atom waitForFileClosedAtom;
164 char* path;
165 struct _FileListEntry *next;
166 } FileListEntry;
168 typedef struct {
169 int waitForOpenCount;
170 int waitForCloseCount;
171 FileListEntry* fileList;
172 } FileListHead;
173 static FileListHead fileListHead;
175 static void setPropertyValue(Atom atom) {
176 XChangeProperty(TheDisplay,
177 RootWindow(TheDisplay, DefaultScreen(TheDisplay)),
178 atom, XA_STRING,
179 8, PropModeReplace,
180 (unsigned char *)"True", 4);
183 /* Add another entry to the file entry list, if it doesn't exist yet. */
184 static void addToFileList(const char *path)
186 FileListEntry *item;
188 /* see if the file already exists in the list */
189 for (item = fileListHead.fileList; item; item = item->next) {
190 if (!strcmp(item->path, path))
191 break;
194 /* Add the atom to the head of the file list if it wasn't found. */
195 if (item == 0) {
196 item = malloc(sizeof(item[0]));
197 item->waitForFileOpenAtom = None;
198 item->waitForFileClosedAtom = None;
199 item->path = (char*)malloc(strlen(path)+1);
200 strcpy(item->path, path);
201 item->next = fileListHead.fileList;
202 fileListHead.fileList = item;
206 /* Creates the properties for the various paths */
207 static void createWaitProperties()
209 FileListEntry *item;
211 for (item = fileListHead.fileList; item; item = item->next) {
212 fileListHead.waitForOpenCount++;
213 item->waitForFileOpenAtom =
214 CreateServerFileOpenAtom(Preferences.serverName, item->path);
215 setPropertyValue(item->waitForFileOpenAtom);
217 if (Preferences.waitForClose == True) {
218 fileListHead.waitForCloseCount++;
219 item->waitForFileClosedAtom =
220 CreateServerFileClosedAtom(Preferences.serverName,
221 item->path,
222 False);
223 setPropertyValue(item->waitForFileClosedAtom);
228 int main(int argc, char **argv)
230 XtAppContext context;
231 Window rootWindow;
232 CommandLine commandLine;
233 Atom serverExistsAtom, serverRequestAtom;
234 XrmDatabase prefDB;
235 Boolean serverExists;
237 /* Initialize toolkit and get an application context */
238 XtToolkitInitialize();
239 AppContext = context = XtCreateApplicationContext();
241 #ifdef VMS
242 /* Convert the command line to Unix style */
243 ConvertVMSCommandLine(&argc, &argv);
244 #endif /*VMS*/
245 #ifdef __EMX__
246 /* expand wildcards if necessary */
247 _wildcard(&argc, &argv);
248 #endif
250 /* Read the preferences command line into a database (note that we
251 don't support the .nc file anymore) */
252 prefDB = CreatePreferencesDatabase(NULL, APP_CLASS,
253 OpTable, XtNumber(OpTable), (unsigned *)&argc, argv);
255 /* Process the command line before calling XtOpenDisplay, because the
256 latter consumes certain command line arguments that we still need
257 (-icon, -geometry ...) */
258 commandLine = processCommandLine(argc, argv);
260 /* Open the display and find the root window */
261 TheDisplay = XtOpenDisplay (context, NULL, APP_NAME, APP_CLASS, NULL,
262 0, &argc, argv);
263 if (!TheDisplay) {
264 XtWarning ("nc: Can't open display\n");
265 exit(EXIT_FAILURE);
267 rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
269 /* Read the application resources into the Preferences data structure */
270 RestorePreferences(prefDB, XtDatabase(TheDisplay), APP_NAME,
271 APP_CLASS, PrefDescrip, XtNumber(PrefDescrip));
273 /* Make sure that the time out unit is at least 1 second and not too
274 large either (overflow!). */
275 if (Preferences.timeOut < 1) {
276 Preferences.timeOut = 1;
277 } else if (Preferences.timeOut > 1000) {
278 Preferences.timeOut = 1000;
281 #ifndef VMS
282 /* For Clearcase users who have not set a server name, use the clearcase
283 view name. Clearcase views make files with the same absolute path names
284 but different contents (and therefore can't be edited in the same nedit
285 session). This should have no bad side-effects for non-clearcase users */
286 if (Preferences.serverName[0] == '\0') {
287 const char* viewTag = GetClearCaseViewTag();
288 if (viewTag != NULL && strlen(viewTag) < MAXPATHLEN) {
289 strcpy(Preferences.serverName, viewTag);
292 #endif /* VMS */
294 /* Create the wait properties for the various files. */
295 createWaitProperties();
297 /* Monitor the properties on the root window */
298 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask);
300 /* Create the server property atoms on the current DISPLAY. */
301 CreateServerPropertyAtoms(Preferences.serverName,
302 &serverExistsAtom,
303 &serverRequestAtom);
305 serverExists = findExistingServer(context,
306 rootWindow,
307 serverExistsAtom);
309 if (serverExists == False)
310 startNewServer(context, rootWindow, commandLine.shell, serverExistsAtom);
312 waitUntilRequestProcessed(context,
313 rootWindow,
314 commandLine.serverRequest,
315 serverRequestAtom);
317 waitUntilFilesOpenedOrClosed(context, rootWindow);
319 XtCloseDisplay(TheDisplay);
320 XtFree(commandLine.shell);
321 XtFree(commandLine.serverRequest);
322 return 0;
327 ** Xt timer procedure for timeouts on NEdit server requests
329 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id)
331 /* NOTE: XtAppNextEvent() does call this routine but
332 ** doesn't return unless there are more events.
333 ** Hence, we generate this (synthetic) event to break the deadlock
335 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
336 if (currentWaitForAtom != noAtom) {
337 XChangeProperty(TheDisplay, rootWindow, currentWaitForAtom, XA_STRING,
338 8, PropModeReplace, (unsigned char *)"", strlen(""));
341 /* Flag that the timeout has occurred. */
342 *timeOutReturn = True;
347 static Boolean findExistingServer(XtAppContext context,
348 Window rootWindow,
349 Atom serverExistsAtom)
351 Boolean serverExists = True;
352 unsigned char *propValue;
353 int getFmt;
354 Atom dummyAtom;
355 unsigned long dummyULong, nItems;
357 /* See if there might be a server (not a guaranty), by translating the
358 root window property NEDIT_SERVER_EXISTS_<user>_<host> */
359 if (XGetWindowProperty(TheDisplay, rootWindow, serverExistsAtom, 0,
360 INT_MAX, False, XA_STRING, &dummyAtom, &getFmt, &nItems,
361 &dummyULong, &propValue) != Success || nItems == 0) {
362 serverExists = False;
363 } else {
364 Boolean timeOut = False;
365 XtIntervalId timerId;
367 XFree(propValue);
369 /* Remove the server exists property to make sure the server is
370 ** running. If it is running it will get recreated.
372 XDeleteProperty(TheDisplay, rootWindow, serverExistsAtom);
373 XSync(TheDisplay, False);
374 timerId = XtAppAddTimeOut(context,
375 PROPERTY_CHANGE_TIMEOUT,
376 (XtTimerCallbackProc)timeOutProc,
377 &timeOut);
378 currentWaitForAtom = serverExistsAtom;
380 while (!timeOut) {
381 /* NOTE: XtAppNextEvent() does call the timeout routine but
382 ** doesn't return unless there are more events. */
383 XEvent event;
384 const XPropertyEvent *e = (const XPropertyEvent *)&event;
385 XtAppNextEvent(context, &event);
387 /* We will get a PropertyNewValue when the server recreates
388 ** the server exists atom. */
389 if (e->type == PropertyNotify &&
390 e->window == rootWindow &&
391 e->atom == serverExistsAtom) {
392 if (e->state == PropertyNewValue) {
393 break;
396 XtDispatchEvent(&event);
399 /* Start a new server if the timeout expired. The server exists
400 ** property was not recreated. */
401 if (timeOut) {
402 serverExists = False;
403 } else {
404 XtRemoveTimeOut(timerId);
408 return(serverExists);
414 static void startNewServer(XtAppContext context,
415 Window rootWindow,
416 char* commandLine,
417 Atom serverExistsAtom)
419 Boolean timeOut = False;
420 XtIntervalId timerId;
422 /* Add back the server name resource from the command line or resource
423 database to the command line for starting the server. If -svrcmd
424 appeared on the original command line, it was removed by
425 CreatePreferencesDatabase before the command line was recorded
426 in commandLine.shell. Moreover, if no server name was specified, it
427 may have defaulted to the ClearCase view tag. */
428 if (Preferences.serverName[0] != '\0') {
429 strcat(commandLine, " -svrname ");
430 strcat(commandLine, Preferences.serverName);
432 switch (startServer("No servers available, start one? (y|n) [y]: ",
433 commandLine))
435 case -1: /* Start failed */
436 XtCloseDisplay(TheDisplay);
437 exit(EXIT_FAILURE);
438 break;
439 case -2: /* Start canceled by user */
440 XtCloseDisplay(TheDisplay);
441 exit(EXIT_SUCCESS);
442 break;
445 /* Set up a timeout proc in case the server is dead. The standard
446 selection timeout is probably a good guess at how long to wait
447 for this style of inter-client communication as well */
448 timerId = XtAppAddTimeOut(context,
449 SERVER_START_TIMEOUT,
450 (XtTimerCallbackProc)timeOutProc,
451 &timeOut);
452 currentWaitForAtom = serverExistsAtom;
454 /* Wait for the server to start */
455 while (!timeOut) {
456 XEvent event;
457 const XPropertyEvent *e = (const XPropertyEvent *)&event;
458 /* NOTE: XtAppNextEvent() does call the timeout routine but
459 ** doesn't return unless there are more events. */
460 XtAppNextEvent(context, &event);
462 /* We will get a PropertyNewValue when the server updates
463 ** the server exists atom. If the property is deleted the
464 ** the server must have died. */
465 if (e->type == PropertyNotify &&
466 e->window == rootWindow &&
467 e->atom == serverExistsAtom) {
468 if (e->state == PropertyNewValue) {
469 break;
470 } else if (e->state == PropertyDelete) {
471 fprintf(stderr, "%s: The server failed to start.\n", APP_NAME);
472 XtCloseDisplay(TheDisplay);
473 exit(EXIT_FAILURE);
476 XtDispatchEvent(&event);
478 /* Exit if the timeout expired. */
479 if (timeOut) {
480 fprintf(stderr, "%s: The server failed to start (time-out).\n", APP_NAME);
481 XtCloseDisplay(TheDisplay);
482 exit(EXIT_FAILURE);
483 } else {
484 XtRemoveTimeOut(timerId);
489 ** Prompt the user about starting a server, with "message", then start server
491 static int startServer(const char *message, const char *commandLineArgs)
493 char c, *commandLine;
494 #ifdef VMS
495 int spawnFlags = 1 /* + 1<<3 */; /* NOWAIT, NOKEYPAD */
496 int spawn_sts;
497 struct dsc$descriptor_s *cmdDesc;
498 char *nulDev = "NL:";
499 struct dsc$descriptor_s *nulDevDesc;
500 #else
501 int sysrc;
502 #endif /* !VMS */
504 /* prompt user whether to start server */
505 if (!Preferences.autoStart) {
506 printf(message);
507 do {
508 c = getc(stdin);
509 } while (c == ' ' || c == '\t');
510 if (c != 'Y' && c != 'y' && c != '\n')
511 return (-2);
514 /* start the server */
515 #ifdef VMS
516 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
517 strlen(commandLineArgs) + 3);
518 sprintf(commandLine, "%s %s", Preferences.serverCmd, commandLineArgs);
519 cmdDesc = NulStrToDesc(commandLine); /* build command descriptor */
520 nulDevDesc = NulStrToDesc(nulDev); /* build "NL:" descriptor */
521 spawn_sts = lib$spawn(cmdDesc, nulDevDesc, 0, &spawnFlags, 0,0,0,0,0,0,0,0);
522 XtFree(commandLine);
523 if (spawn_sts != SS$_NORMAL) {
524 fprintf(stderr, "Error return from lib$spawn: %d\n", spawn_sts);
525 fprintf(stderr, "Nedit server not started.\n");
526 return (-1);
527 } else {
528 FreeStrDesc(cmdDesc);
529 return 0;
531 #else
532 #if defined(__EMX__) /* OS/2 */
533 /* Unfortunately system() calls a shell determined by the environment
534 variables COMSPEC and EMXSHELL. We have to figure out which one. */
536 char *sh_spec, *sh, *base;
537 char *CMD_EXE="cmd.exe";
539 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
540 strlen(commandLineArgs) + 15);
541 sh = getenv ("EMXSHELL");
542 if (sh == NULL)
543 sh = getenv ("COMSPEC");
544 if (sh == NULL)
545 sh = CMD_EXE;
546 sh_spec=XtNewString(sh);
547 base=_getname(sh_spec);
548 _remext(base);
549 if (stricmp (base, "cmd") == 0 || stricmp (base, "4os2") == 0) {
550 sprintf(commandLine, "start /C /MIN %s %s",
551 Preferences.serverCmd, commandLineArgs);
552 } else {
553 sprintf(commandLine, "%s %s &",
554 Preferences.serverCmd, commandLineArgs);
556 XtFree(sh_spec);
558 #else /* Unix */
559 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
560 strlen(commandLineArgs) + 3);
561 sprintf(commandLine, "%s %s&", Preferences.serverCmd, commandLineArgs);
562 #endif
564 sysrc=system(commandLine);
565 XtFree(commandLine);
567 if (sysrc==0)
568 return 0;
569 else
570 return (-1);
571 #endif /* !VMS */
574 /* Reconstruct the command line in string commandLine in case we have to
575 * start a server (nc command line args parallel nedit's). Include
576 * -svrname if nc wants a named server, so nedit will match. Special
577 * characters are protected from the shell by escaping EVERYTHING with \
579 static CommandLine processCommandLine(int argc, char** argv)
581 CommandLine commandLine;
582 int i;
583 int length = 0;
585 for (i=1; i<argc; i++) {
586 length += 1 + strlen(argv[i])*4 + 2;
588 commandLine.shell = XtMalloc(length+1 + 9 + MAXPATHLEN);
589 *commandLine.shell = '\0';
591 /* Convert command line arguments into a command string for the server */
592 parseCommandLine(argc, argv, &commandLine);
593 if (commandLine.serverRequest == NULL) {
594 fprintf(stderr, "nc: Invalid commandline argument\n");
595 exit(EXIT_FAILURE);
598 return(commandLine);
603 ** Converts command line into a command string suitable for passing to
604 ** the server
606 static void parseCommandLine(int argc, char **argv, CommandLine *commandLine)
608 #define MAX_RECORD_HEADER_LENGTH 38
609 char name[MAXPATHLEN], path[MAXPATHLEN];
610 const char *toDoCommand = "", *langMode = "", *geometry = "";
611 char *commandString, *outPtr;
612 int lineNum = 0, read = 0, create = 0, iconic = 0, tabbed = -1, length = 0;
613 int i, lineArg, nRead, charsWritten, opts = True;
614 int fileCount = 0, group = 0, isTabbed;
616 /* Allocate a string for output, for the maximum possible length. The
617 maximum length is calculated by assuming every argument is a file,
618 and a complete record of maximum length is created for it */
619 for (i=1; i<argc; i++) {
620 length += MAX_RECORD_HEADER_LENGTH + strlen(argv[i]) + MAXPATHLEN;
622 /* In case of no arguments, must still allocate space for one record header */
623 if (length < MAX_RECORD_HEADER_LENGTH)
625 length = MAX_RECORD_HEADER_LENGTH;
627 commandString = XtMalloc(length+1);
629 /* Parse the arguments and write the output string */
630 outPtr = commandString;
631 for (i=1; i<argc; i++) {
632 if (opts && !strcmp(argv[i], "--")) {
633 opts = False; /* treat all remaining arguments as filenames */
634 continue;
635 } else if (opts && !strcmp(argv[i], "-do")) {
636 nextArg(argc, argv, &i);
637 toDoCommand = argv[i];
638 } else if (opts && !strcmp(argv[i], "-lm")) {
639 copyCommandLineArg(commandLine, argv[i]);
640 nextArg(argc, argv, &i);
641 langMode = argv[i];
642 copyCommandLineArg(commandLine, argv[i]);
643 } else if (opts && (!strcmp(argv[i], "-g") ||
644 !strcmp(argv[i], "-geometry"))) {
645 copyCommandLineArg(commandLine, argv[i]);
646 nextArg(argc, argv, &i);
647 geometry = argv[i];
648 copyCommandLineArg(commandLine, argv[i]);
649 } else if (opts && !strcmp(argv[i], "-read")) {
650 read = 1;
651 } else if (opts && !strcmp(argv[i], "-create")) {
652 create = 1;
653 } else if (opts && !strcmp(argv[i], "-tabbed")) {
654 tabbed = 1;
655 group = 0; /* override -group option */
656 } else if (opts && !strcmp(argv[i], "-untabbed")) {
657 tabbed = 0;
658 group = 0; /* override -group option */
659 } else if (opts && !strcmp(argv[i], "-group")) {
660 group = 2; /* 2: start new group, 1: in group */
661 } else if (opts && (!strcmp(argv[i], "-iconic") ||
662 !strcmp(argv[i], "-icon"))) {
663 iconic = 1;
664 copyCommandLineArg(commandLine, argv[i]);
665 } else if (opts && !strcmp(argv[i], "-line")) {
666 nextArg(argc, argv, &i);
667 nRead = sscanf(argv[i], "%d", &lineArg);
668 if (nRead != 1)
669 fprintf(stderr, "nc: argument to line should be a number\n");
670 else
671 lineNum = lineArg;
672 } else if (opts && (*argv[i] == '+')) {
673 nRead = sscanf((argv[i]+1), "%d", &lineArg);
674 if (nRead != 1)
675 fprintf(stderr, "nc: argument to + should be a number\n");
676 else
677 lineNum = lineArg;
678 } else if (opts && (!strcmp(argv[i], "-ask") || !strcmp(argv[i], "-noask"))) {
679 ; /* Ignore resource-based arguments which are processed later */
680 } else if (opts && (!strcmp(argv[i], "-svrname") ||
681 !strcmp(argv[i], "-svrcmd"))) {
682 nextArg(argc, argv, &i); /* Ignore rsrc args with data */
683 } else if (opts && (!strcmp(argv[i], "-xrm") ||
684 !strcmp(argv[i], "-display"))) {
685 copyCommandLineArg(commandLine, argv[i]);
686 nextArg(argc, argv, &i); /* Ignore rsrc args with data */
687 copyCommandLineArg(commandLine, argv[i]);
688 } else if (opts && (!strcmp(argv[i], "-version") || !strcmp(argv[i], "-V"))) {
689 printNcVersion();
690 exit(EXIT_SUCCESS);
691 } else if (opts && (!strcmp(argv[i], "-h") ||
692 !strcmp(argv[i], "-help"))) {
693 fprintf(stderr, "%s", cmdLineHelp);
694 exit(EXIT_SUCCESS);
695 } else if (opts && (*argv[i] == '-')) {
696 #ifdef VMS
697 *argv[i] = '/';
698 #endif /*VMS*/
699 fprintf(stderr, "nc: Unrecognized option %s\n%s", argv[i],
700 cmdLineHelp);
701 exit(EXIT_FAILURE);
702 } else {
703 #ifdef VMS
704 int numFiles, j, oldLength;
705 char **nameList = NULL, *newCommandString;
706 /* Use VMS's LIB$FILESCAN for filename in argv[i] to process */
707 /* wildcards and to obtain a full VMS file specification */
708 numFiles = VMSFileScan(argv[i], &nameList, NULL, INCLUDE_FNF);
709 /* for each expanded file name do: */
710 for (j = 0; j < numFiles; ++j) {
711 oldLength = outPtr-commandString;
712 newCommandString = XtMalloc(oldLength+length+1);
713 strncpy(newCommandString, commandString, oldLength);
714 XtFree(commandString);
715 commandString = newCommandString;
716 outPtr = newCommandString + oldLength;
717 if (ParseFilename(nameList[j], name, path) != 0) {
718 /* An Error, most likely too long paths/strings given */
719 commandLine->serverRequest = NULL;
720 return;
722 strcat(path, name);
724 /* determine if file is to be openned in new tab, by
725 factoring the options -group, -tabbed & -untabbed */
726 if (group == 2) {
727 isTabbed = 0; /* start a new window for new group */
728 group = 1; /* next file will be within group */
730 else if (group == 1) {
731 isTabbed = 1; /* new tab for file in group */
733 else {
734 isTabbed = tabbed; /* not in group */
737 /* See below for casts */
738 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%s\n%s\n%s\n%s\n%n",
739 lineNum, read, create, iconic, tabbed, (long) strlen(path),
740 (long) strlen(toDoCommand), (long) strlen(langMode),
741 (long) strlen(geometry),
742 path, toDoCommand, langMode, geometry, &charsWritten);
743 outPtr += charsWritten;
744 free(nameList[j]);
746 /* Create the file open atoms for the paths supplied */
747 addToFileList(path);
748 fileCount++;
750 if (nameList != NULL)
751 free(nameList);
752 #else
753 if (ParseFilename(argv[i], name, path) != 0) {
754 /* An Error, most likely too long paths/strings given */
755 commandLine->serverRequest = NULL;
756 return;
758 strcat(path, name);
760 /* determine if file is to be openned in new tab, by
761 factoring the options -group, -tabbed & -untabbed */
762 if (group == 2) {
763 isTabbed = 0; /* start a new window for new group */
764 group = 1; /* next file will be within group */
766 else if (group == 1) {
767 isTabbed = 1; /* new tab for file in group */
769 else {
770 isTabbed = tabbed; /* not in group */
773 /* SunOS 4 acc or acc and/or its runtime library has a bug
774 such that %n fails (segv) if it follows a string in a
775 printf or sprintf. The silly code below avoids this.
777 The "long" cast on strlen() is necessary because size_t
778 is 64 bit on Alphas, and 32-bit on most others. There is
779 no printf format specifier for "size_t", thanx, ANSI. */
780 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%n", lineNum,
781 read, create, iconic, isTabbed, (long) strlen(path),
782 (long) strlen(toDoCommand), (long) strlen(langMode),
783 (long) strlen(geometry), &charsWritten);
784 outPtr += charsWritten;
785 strcpy(outPtr, path);
786 outPtr += strlen(path);
787 *outPtr++ = '\n';
788 strcpy(outPtr, toDoCommand);
789 outPtr += strlen(toDoCommand);
790 *outPtr++ = '\n';
791 strcpy(outPtr, langMode);
792 outPtr += strlen(langMode);
793 *outPtr++ = '\n';
794 strcpy(outPtr, geometry);
795 outPtr += strlen(geometry);
796 *outPtr++ = '\n';
797 toDoCommand = "";
799 /* Create the file open atoms for the paths supplied */
800 addToFileList(path);
801 fileCount++;
802 #endif /* VMS */
805 #ifdef VMS
806 VMSFileScanDone();
807 #endif /*VMS*/
809 /* If there's an un-written -do command, or we are to open a new window,
810 * or user has requested iconic state, but not provided a file name,
811 * create a server request with an empty file name and requested
812 * iconic state (and optional language mode and geometry).
814 if (toDoCommand[0] != '\0' || fileCount == 0) {
815 sprintf(outPtr, "0 0 0 %d %d 0 %ld %ld %ld\n\n%n", iconic, tabbed,
816 (long) strlen(toDoCommand),
817 (long) strlen(langMode), (long) strlen(geometry), &charsWritten);
818 outPtr += charsWritten;
819 strcpy(outPtr, toDoCommand);
820 outPtr += strlen(toDoCommand);
821 *outPtr++ = '\n';
822 strcpy(outPtr, langMode);
823 outPtr += strlen(langMode);
824 *outPtr++ = '\n';
825 strcpy(outPtr, geometry);
826 outPtr += strlen(geometry);
827 *outPtr++ = '\n';
830 *outPtr = '\0';
831 commandLine->serverRequest = commandString;
835 static void waitUntilRequestProcessed(XtAppContext context,
836 Window rootWindow,
837 char* commandString,
838 Atom serverRequestAtom)
840 XtIntervalId timerId;
841 Boolean timeOut = False;
843 /* Set the NEDIT_SERVER_REQUEST_<user>_<host> property on the root
844 window to activate the server */
845 XChangeProperty(TheDisplay, rootWindow, serverRequestAtom, XA_STRING, 8,
846 PropModeReplace, (unsigned char *)commandString,
847 strlen(commandString));
849 /* Set up a timeout proc in case the server is dead. The standard
850 selection timeout is probably a good guess at how long to wait
851 for this style of inter-client communication as well */
852 timerId = XtAppAddTimeOut(context,
853 REQUEST_TIMEOUT,
854 (XtTimerCallbackProc)timeOutProc,
855 &timeOut);
856 currentWaitForAtom = serverRequestAtom;
858 /* Wait for the property to be deleted to know the request was processed */
859 while (!timeOut) {
860 XEvent event;
861 const XPropertyEvent *e = (const XPropertyEvent *)&event;
863 XtAppNextEvent(context, &event);
864 if (e->window == rootWindow &&
865 e->atom == serverRequestAtom &&
866 e->state == PropertyDelete)
867 break;
868 XtDispatchEvent(&event);
871 /* Exit if the timeout expired. */
872 if (timeOut) {
873 fprintf(stderr, "%s: The server did not respond to the request.\n", APP_NAME);
874 XtCloseDisplay(TheDisplay);
875 exit(EXIT_FAILURE);
876 } else {
877 XtRemoveTimeOut(timerId);
881 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
882 Window rootWindow)
884 XtIntervalId timerId;
885 Boolean timeOut = False;
887 /* Set up a timeout proc so we don't wait forever if the server is dead.
888 The standard selection timeout is probably a good guess at how
889 long to wait for this style of inter-client communication as
890 well */
891 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
892 (XtTimerCallbackProc)timeOutProc, &timeOut);
893 currentWaitForAtom = noAtom;
895 /* Wait for all of the windows to be opened by server,
896 * and closed if -wait was supplied */
897 while (fileListHead.fileList) {
898 XEvent event;
899 const XPropertyEvent *e = (const XPropertyEvent *)&event;
901 XtAppNextEvent(context, &event);
903 /* Update the fileList and check if all files have been closed. */
904 if (e->type == PropertyNotify && e->window == rootWindow) {
905 FileListEntry *item;
907 if (e->state == PropertyDelete) {
908 for (item = fileListHead.fileList; item; item = item->next) {
909 if (e->atom == item->waitForFileOpenAtom) {
910 /* The 'waitForFileOpen' property is deleted when the file is opened */
911 fileListHead.waitForOpenCount--;
912 item->waitForFileOpenAtom = None;
914 /* Reset the timer while we wait for all files to be opened. */
915 XtRemoveTimeOut(timerId);
916 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
917 (XtTimerCallbackProc)timeOutProc, &timeOut);
918 } else if (e->atom == item->waitForFileClosedAtom) {
919 /* When file is opened in -wait mode the property
920 * is deleted when the file is closed.
922 fileListHead.waitForCloseCount--;
923 item->waitForFileClosedAtom = None;
927 if (fileListHead.waitForOpenCount == 0 && !timeOut) {
928 XtRemoveTimeOut(timerId);
931 if (fileListHead.waitForOpenCount == 0 &&
932 fileListHead.waitForCloseCount == 0) {
933 break;
938 /* We are finished if we are only waiting for files to open and
939 ** the file open timeout has expired. */
940 if (!Preferences.waitForClose && timeOut) {
941 break;
944 XtDispatchEvent(&event);
949 static void nextArg(int argc, char **argv, int *argIndex)
951 if (*argIndex + 1 >= argc) {
952 #ifdef VMS
953 *argv[*argIndex] = '/';
954 #endif /*VMS*/
955 fprintf(stderr, "nc: %s requires an argument\n%s",
956 argv[*argIndex], cmdLineHelp);
957 exit(EXIT_FAILURE);
959 (*argIndex)++;
962 /* Copies a given nc command line argument to the server startup command
963 ** line (-icon, -geometry, -xrm, ...) Special characters are protected from
964 ** the shell by escaping EVERYTHING with \
965 ** Note that the .shell string in the command line structure is large enough
966 ** to hold the escaped characters.
968 static void copyCommandLineArg(CommandLine *commandLine, const char *arg)
970 const char *c;
971 char *outPtr = commandLine->shell + strlen(commandLine->shell);
972 #if defined(VMS) || defined(__EMX__)
973 /* Non-Unix shells don't want/need esc */
974 for (c=arg; *c!='\0'; c++) {
975 *outPtr++ = *c;
977 *outPtr++ = ' ';
978 *outPtr = '\0';
979 #else
980 *outPtr++ = '\'';
981 for (c=arg; *c!='\0'; c++) {
982 if (*c == '\'') {
983 *outPtr++ = '\'';
984 *outPtr++ = '\\';
986 *outPtr++ = *c;
987 if (*c == '\'') {
988 *outPtr++ = '\'';
991 *outPtr++ = '\'';
992 *outPtr++ = ' ';
993 *outPtr = '\0';
994 #endif /* VMS */
997 /* Print version of 'nc' */
998 static void printNcVersion(void ) {
999 static const char *const ncHelpText = \
1000 "nc (NEdit) Version 5.5 (October 2004)\n\n\
1001 Built on: %s, %s, %s\n\
1002 Built at: %s, %s\n";
1004 fprintf(stdout, ncHelpText,
1005 COMPILE_OS, COMPILE_MACHINE, COMPILE_COMPILER,
1006 __DATE__, __TIME__);