1 static const char CVSID
[] = "$Id: nc.c,v 1.39 2004/02/19 06:23:14 tksoh Exp $";
2 /*******************************************************************************
4 * nc.c -- Nirvana Editor client program for nedit server processes *
6 * Copyright (C) 1999 Mark Edel *
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 *
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 *
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 *
22 * Nirvana Text Editor *
25 * Written by Mark Edel *
27 *******************************************************************************/
30 #include "../config.h"
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"
44 #include <lib$routines.h>
48 #include "../util/VMSparam.h"
49 #include "../util/VMSutils.h"
52 #include <sys/param.h>
54 #include <sys/types.h>
55 #include <sys/utsname.h>
58 #include "../util/clearcase.h"
64 #include <X11/Intrinsic.h>
65 #include <X11/Xatom.h>
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 */
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
,
94 Atom serverExistsAtom
);
95 static void startNewServer(XtAppContext context
,
98 Atom serverExistsAtom
);
99 static void waitUntilRequestProcessed(XtAppContext context
,
102 Atom serverRequestAtom
);
103 static void waitUntilFilesOpenedOrClosed(XtAppContext context
,
107 XtAppContext AppContext
;
108 static Atom currentWaitForAtom
;
109 static Atom noAtom
= (Atom
)(-1);
111 static const char cmdLineHelp
[] =
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"
122 " [-xrm resourcestring] [-display [host]:server[.screen]]\n"
126 /* Structure to hold X Resource values */
129 char serverCmd
[2*MAXPATHLEN
]; /* holds executable name + flags */
130 char serverName
[MAXPATHLEN
];
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
;
164 struct _FileListEntry
*next
;
168 int waitForOpenCount
;
169 int waitForCloseCount
;
170 FileListEntry
* fileList
;
172 static FileListHead fileListHead
;
174 static void setPropertyValue(Atom atom
) {
175 XChangeProperty(TheDisplay
,
176 RootWindow(TheDisplay
, DefaultScreen(TheDisplay
)),
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
)
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
))
193 /* Add the atom to the head of the file list if it wasn't found. */
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()
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
,
222 setPropertyValue(item
->waitForFileClosedAtom
);
227 int main(int argc
, char **argv
)
229 XtAppContext context
;
231 CommandLine commandLine
;
232 Atom serverExistsAtom
, serverRequestAtom
;
234 Boolean serverExists
;
236 /* Initialize toolkit and get an application context */
237 XtToolkitInitialize();
238 AppContext
= context
= XtCreateApplicationContext();
241 /* Convert the command line to Unix style */
242 ConvertVMSCommandLine(&argc
, &argv
);
245 /* expand wildcards if necessary */
246 _wildcard(&argc
, &argv
);
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
,
263 XtWarning ("nc: Can't open display\n");
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;
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
);
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
,
304 serverExists
= findExistingServer(context
,
308 if (serverExists
== False
)
309 startNewServer(context
, rootWindow
, commandLine
.shell
, serverExistsAtom
);
311 waitUntilRequestProcessed(context
,
313 commandLine
.serverRequest
,
316 waitUntilFilesOpenedOrClosed(context
, rootWindow
);
318 XtCloseDisplay(TheDisplay
);
319 XtFree(commandLine
.shell
);
320 XtFree(commandLine
.serverRequest
);
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
,
348 Atom serverExistsAtom
)
350 Boolean serverExists
= True
;
351 unsigned char *propValue
;
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
;
363 Boolean timeOut
= False
;
364 XtIntervalId timerId
;
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
,
377 currentWaitForAtom
= serverExistsAtom
;
380 /* NOTE: XtAppNextEvent() does call the timeout routine but
381 ** doesn't return unless there are more events. */
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
) {
395 XtDispatchEvent(&event
);
398 /* Start a new server if the timeout expired. The server exists
399 ** property was not recreated. */
401 serverExists
= False
;
403 XtRemoveTimeOut(timerId
);
407 return(serverExists
);
413 static void startNewServer(XtAppContext context
,
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]: ",
434 case -1: /* Start failed */
435 XtCloseDisplay(TheDisplay
);
438 case -2: /* Start canceled by user */
439 XtCloseDisplay(TheDisplay
);
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
,
451 currentWaitForAtom
= serverExistsAtom
;
453 /* Wait for the server to start */
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
) {
469 } else if (e
->state
== PropertyDelete
) {
470 fprintf(stderr
, "%s: The server failed to start.\n", APP_NAME
);
471 XtCloseDisplay(TheDisplay
);
475 XtDispatchEvent(&event
);
477 /* Exit if the timeout expired. */
479 fprintf(stderr
, "%s: The server failed to start (time-out).\n", APP_NAME
);
480 XtCloseDisplay(TheDisplay
);
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
;
494 int spawnFlags
= 1 /* + 1<<3 */; /* NOWAIT, NOKEYPAD */
496 struct dsc$descriptor_s
*cmdDesc
;
497 char *nulDev
= "NL:";
498 struct dsc$descriptor_s
*nulDevDesc
;
503 /* prompt user whether to start server */
504 if (!Preferences
.autoStart
) {
508 } while (c
== ' ' || c
== '\t');
509 if (c
!= 'Y' && c
!= 'y' && c
!= '\n')
513 /* start the server */
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);
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");
527 FreeStrDesc(cmdDesc
);
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");
542 sh
= getenv ("COMSPEC");
545 sh_spec
=XtNewString(sh
);
546 base
=_getname(sh_spec
);
548 if (stricmp (base
, "cmd") == 0 || stricmp (base
, "4os2") == 0) {
549 sprintf(commandLine
, "start /C /MIN %s %s",
550 Preferences
.serverCmd
, commandLineArgs
);
552 sprintf(commandLine
, "%s %s &",
553 Preferences
.serverCmd
, commandLineArgs
);
558 commandLine
= XtMalloc(strlen(Preferences
.serverCmd
) +
559 strlen(commandLineArgs
) + 3);
560 sprintf(commandLine
, "%s %s&", Preferences
.serverCmd
, commandLineArgs
);
563 sysrc
=system(commandLine
);
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
;
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");
602 ** Converts command line into a command string suitable for passing to
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 */
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
);
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
);
647 copyCommandLineArg(commandLine
, argv
[i
]);
648 } else if (opts
&& !strcmp(argv
[i
], "-read")) {
650 } else if (opts
&& !strcmp(argv
[i
], "-create")) {
652 } else if (opts
&& !strcmp(argv
[i
], "-tabbed")) {
654 group
= 0; /* override -group option */
655 } else if (opts
&& !strcmp(argv
[i
], "-untabbed")) {
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"))) {
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
);
668 fprintf(stderr
, "nc: argument to line should be a number\n");
671 } else if (opts
&& (*argv
[i
] == '+')) {
672 nRead
= sscanf((argv
[i
]+1), "%d", &lineArg
);
674 fprintf(stderr
, "nc: argument to + should be a number\n");
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"))) {
690 } else if (opts
&& (*argv
[i
] == '-')) {
694 fprintf(stderr
, "nc: Unrecognized option %s\n%s", argv
[i
],
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
;
719 /* determine if file is to be openned in new tab, by
720 factoring the options -group, -tabbed & -untabbed */
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 */
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
;
741 /* Create the file open atoms for the paths supplied */
745 if (nameList
!= NULL
)
748 if (ParseFilename(argv
[i
], name
, path
) != 0) {
749 /* An Error, most likely too long paths/strings given */
750 commandLine
->serverRequest
= NULL
;
755 /* determine if file is to be openned in new tab, by
756 factoring the options -group, -tabbed & -untabbed */
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 */
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
);
783 strcpy(outPtr
, toDoCommand
);
784 outPtr
+= strlen(toDoCommand
);
786 strcpy(outPtr
, langMode
);
787 outPtr
+= strlen(langMode
);
789 strcpy(outPtr
, geometry
);
790 outPtr
+= strlen(geometry
);
794 /* Create the file open atoms for the paths supplied */
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
);
817 strcpy(outPtr
, langMode
);
818 outPtr
+= strlen(langMode
);
820 strcpy(outPtr
, geometry
);
821 outPtr
+= strlen(geometry
);
826 commandLine
->serverRequest
= commandString
;
830 static void waitUntilRequestProcessed(XtAppContext context
,
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
,
849 (XtTimerCallbackProc
)timeOutProc
,
851 currentWaitForAtom
= serverRequestAtom
;
853 /* Wait for the property to be deleted to know the request was processed */
856 const XPropertyEvent
*e
= (const XPropertyEvent
*)&event
;
858 XtAppNextEvent(context
, &event
);
859 if (e
->window
== rootWindow
&&
860 e
->atom
== serverRequestAtom
&&
861 e
->state
== PropertyDelete
)
863 XtDispatchEvent(&event
);
866 /* Exit if the timeout expired. */
868 fprintf(stderr
, "%s: The server did not respond to the request.\n", APP_NAME
);
869 XtCloseDisplay(TheDisplay
);
872 XtRemoveTimeOut(timerId
);
876 static void waitUntilFilesOpenedOrClosed(XtAppContext context
,
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
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
) {
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
) {
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) {
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
) {
939 XtDispatchEvent(&event
);
944 static void nextArg(int argc
, char **argv
, int *argIndex
)
946 if (*argIndex
+ 1 >= argc
) {
948 *argv
[*argIndex
] = '/';
950 fprintf(stderr
, "nc: %s requires an argument\n%s",
951 argv
[*argIndex
], cmdLineHelp
);
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
)
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
++) {
976 for (c
=arg
; *c
!='\0'; c
++) {
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\
999 fprintf(stdout
, ncHelpText
,
1000 COMPILE_OS
, COMPILE_MACHINE
, COMPILE_COMPILER
,
1001 __DATE__
, __TIME__
);