Fix for SF bug #1060749: focusWindowMS (macro.c) causes crash.
[nedit.git] / source / nc.c
blobb1f522d3bdef799fdfad55b004f27f8f0c32e7b6
1 static const char CVSID[] = "$Id: nc.c,v 1.45 2004/09/30 20:51:05 n8gray 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
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]\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 puts(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 && (*argv[i] == '-')) {
692 #ifdef VMS
693 *argv[i] = '/';
694 #endif /*VMS*/
695 fprintf(stderr, "nc: Unrecognized option %s\n%s", argv[i],
696 cmdLineHelp);
697 exit(EXIT_FAILURE);
698 } else {
699 #ifdef VMS
700 int numFiles, j, oldLength;
701 char **nameList = NULL, *newCommandString;
702 /* Use VMS's LIB$FILESCAN for filename in argv[i] to process */
703 /* wildcards and to obtain a full VMS file specification */
704 numFiles = VMSFileScan(argv[i], &nameList, NULL, INCLUDE_FNF);
705 /* for each expanded file name do: */
706 for (j = 0; j < numFiles; ++j) {
707 oldLength = outPtr-commandString;
708 newCommandString = XtMalloc(oldLength+length+1);
709 strncpy(newCommandString, commandString, oldLength);
710 XtFree(commandString);
711 commandString = newCommandString;
712 outPtr = newCommandString + oldLength;
713 if (ParseFilename(nameList[j], name, path) != 0) {
714 /* An Error, most likely too long paths/strings given */
715 commandLine->serverRequest = NULL;
716 return;
718 strcat(path, name);
720 /* determine if file is to be openned in new tab, by
721 factoring the options -group, -tabbed & -untabbed */
722 if (group == 2) {
723 isTabbed = 0; /* start a new window for new group */
724 group = 1; /* next file will be within group */
726 else if (group == 1) {
727 isTabbed = 1; /* new tab for file in group */
729 else {
730 isTabbed = tabbed; /* not in group */
733 /* See below for casts */
734 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%s\n%s\n%s\n%s\n%n",
735 lineNum, read, create, iconic, tabbed, (long) strlen(path),
736 (long) strlen(toDoCommand), (long) strlen(langMode),
737 (long) strlen(geometry),
738 path, toDoCommand, langMode, geometry, &charsWritten);
739 outPtr += charsWritten;
740 free(nameList[j]);
742 /* Create the file open atoms for the paths supplied */
743 addToFileList(path);
744 fileCount++;
746 if (nameList != NULL)
747 free(nameList);
748 #else
749 if (ParseFilename(argv[i], name, path) != 0) {
750 /* An Error, most likely too long paths/strings given */
751 commandLine->serverRequest = NULL;
752 return;
754 strcat(path, name);
756 /* determine if file is to be openned in new tab, by
757 factoring the options -group, -tabbed & -untabbed */
758 if (group == 2) {
759 isTabbed = 0; /* start a new window for new group */
760 group = 1; /* next file will be within group */
762 else if (group == 1) {
763 isTabbed = 1; /* new tab for file in group */
765 else {
766 isTabbed = tabbed; /* not in group */
769 /* SunOS 4 acc or acc and/or its runtime library has a bug
770 such that %n fails (segv) if it follows a string in a
771 printf or sprintf. The silly code below avoids this.
773 The "long" cast on strlen() is necessary because size_t
774 is 64 bit on Alphas, and 32-bit on most others. There is
775 no printf format specifier for "size_t", thanx, ANSI. */
776 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%n", lineNum,
777 read, create, iconic, isTabbed, (long) strlen(path),
778 (long) strlen(toDoCommand), (long) strlen(langMode),
779 (long) strlen(geometry), &charsWritten);
780 outPtr += charsWritten;
781 strcpy(outPtr, path);
782 outPtr += strlen(path);
783 *outPtr++ = '\n';
784 strcpy(outPtr, toDoCommand);
785 outPtr += strlen(toDoCommand);
786 *outPtr++ = '\n';
787 strcpy(outPtr, langMode);
788 outPtr += strlen(langMode);
789 *outPtr++ = '\n';
790 strcpy(outPtr, geometry);
791 outPtr += strlen(geometry);
792 *outPtr++ = '\n';
793 toDoCommand = "";
795 /* Create the file open atoms for the paths supplied */
796 addToFileList(path);
797 fileCount++;
798 #endif /* VMS */
801 #ifdef VMS
802 VMSFileScanDone();
803 #endif /*VMS*/
805 /* If there's an un-written -do command, or we are to open a new window,
806 * or user has requested iconic state, but not provided a file name,
807 * create a server request with an empty file name and requested
808 * iconic state (and optional language mode and geometry).
810 if (toDoCommand[0] != '\0' || fileCount == 0) {
811 sprintf(outPtr, "0 0 0 %d %d 0 %ld %ld %ld\n\n%n", iconic, tabbed,
812 (long) strlen(toDoCommand),
813 (long) strlen(langMode), (long) strlen(geometry), &charsWritten);
814 outPtr += charsWritten;
815 strcpy(outPtr, toDoCommand);
816 outPtr += strlen(toDoCommand);
817 *outPtr++ = '\n';
818 strcpy(outPtr, langMode);
819 outPtr += strlen(langMode);
820 *outPtr++ = '\n';
821 strcpy(outPtr, geometry);
822 outPtr += strlen(geometry);
823 *outPtr++ = '\n';
826 *outPtr = '\0';
827 commandLine->serverRequest = commandString;
831 static void waitUntilRequestProcessed(XtAppContext context,
832 Window rootWindow,
833 char* commandString,
834 Atom serverRequestAtom)
836 XtIntervalId timerId;
837 Boolean timeOut = False;
839 /* Set the NEDIT_SERVER_REQUEST_<user>_<host> property on the root
840 window to activate the server */
841 XChangeProperty(TheDisplay, rootWindow, serverRequestAtom, XA_STRING, 8,
842 PropModeReplace, (unsigned char *)commandString,
843 strlen(commandString));
845 /* Set up a timeout proc in case the server is dead. The standard
846 selection timeout is probably a good guess at how long to wait
847 for this style of inter-client communication as well */
848 timerId = XtAppAddTimeOut(context,
849 REQUEST_TIMEOUT,
850 (XtTimerCallbackProc)timeOutProc,
851 &timeOut);
852 currentWaitForAtom = serverRequestAtom;
854 /* Wait for the property to be deleted to know the request was processed */
855 while (!timeOut) {
856 XEvent event;
857 const XPropertyEvent *e = (const XPropertyEvent *)&event;
859 XtAppNextEvent(context, &event);
860 if (e->window == rootWindow &&
861 e->atom == serverRequestAtom &&
862 e->state == PropertyDelete)
863 break;
864 XtDispatchEvent(&event);
867 /* Exit if the timeout expired. */
868 if (timeOut) {
869 fprintf(stderr, "%s: The server did not respond to the request.\n", APP_NAME);
870 XtCloseDisplay(TheDisplay);
871 exit(EXIT_FAILURE);
872 } else {
873 XtRemoveTimeOut(timerId);
877 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
878 Window rootWindow)
880 XtIntervalId timerId;
881 Boolean timeOut = False;
883 /* Set up a timeout proc so we don't wait forever if the server is dead.
884 The standard selection timeout is probably a good guess at how
885 long to wait for this style of inter-client communication as
886 well */
887 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
888 (XtTimerCallbackProc)timeOutProc, &timeOut);
889 currentWaitForAtom = noAtom;
891 /* Wait for all of the windows to be opened by server,
892 * and closed if -wait was supplied */
893 while (fileListHead.fileList) {
894 XEvent event;
895 const XPropertyEvent *e = (const XPropertyEvent *)&event;
897 XtAppNextEvent(context, &event);
899 /* Update the fileList and check if all files have been closed. */
900 if (e->type == PropertyNotify && e->window == rootWindow) {
901 FileListEntry *item;
903 if (e->state == PropertyDelete) {
904 for (item = fileListHead.fileList; item; item = item->next) {
905 if (e->atom == item->waitForFileOpenAtom) {
906 /* The 'waitForFileOpen' property is deleted when the file is opened */
907 fileListHead.waitForOpenCount--;
908 item->waitForFileOpenAtom = None;
910 /* Reset the timer while we wait for all files to be opened. */
911 XtRemoveTimeOut(timerId);
912 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
913 (XtTimerCallbackProc)timeOutProc, &timeOut);
914 } else if (e->atom == item->waitForFileClosedAtom) {
915 /* When file is opened in -wait mode the property
916 * is deleted when the file is closed.
918 fileListHead.waitForCloseCount--;
919 item->waitForFileClosedAtom = None;
923 if (fileListHead.waitForOpenCount == 0 && !timeOut) {
924 XtRemoveTimeOut(timerId);
927 if (fileListHead.waitForOpenCount == 0 &&
928 fileListHead.waitForCloseCount == 0) {
929 break;
934 /* We are finished if we are only waiting for files to open and
935 ** the file open timeout has expired. */
936 if (!Preferences.waitForClose && timeOut) {
937 break;
940 XtDispatchEvent(&event);
945 static void nextArg(int argc, char **argv, int *argIndex)
947 if (*argIndex + 1 >= argc) {
948 #ifdef VMS
949 *argv[*argIndex] = '/';
950 #endif /*VMS*/
951 fprintf(stderr, "nc: %s requires an argument\n%s",
952 argv[*argIndex], cmdLineHelp);
953 exit(EXIT_FAILURE);
955 (*argIndex)++;
958 /* Copies a given nc command line argument to the server startup command
959 ** line (-icon, -geometry, -xrm, ...) Special characters are protected from
960 ** the shell by escaping EVERYTHING with \
961 ** Note that the .shell string in the command line structure is large enough
962 ** to hold the escaped characters.
964 static void copyCommandLineArg(CommandLine *commandLine, const char *arg)
966 const char *c;
967 char *outPtr = commandLine->shell + strlen(commandLine->shell);
968 #if defined(VMS) || defined(__EMX__)
969 /* Non-Unix shells don't want/need esc */
970 for (c=arg; *c!='\0'; c++) {
971 *outPtr++ = *c;
973 *outPtr++ = ' ';
974 *outPtr = '\0';
975 #else
976 *outPtr++ = '\'';
977 for (c=arg; *c!='\0'; c++) {
978 if (*c == '\'') {
979 *outPtr++ = '\'';
980 *outPtr++ = '\\';
982 *outPtr++ = *c;
983 if (*c == '\'') {
984 *outPtr++ = '\'';
987 *outPtr++ = '\'';
988 *outPtr++ = ' ';
989 *outPtr = '\0';
990 #endif /* VMS */
993 /* Print version of 'nc' */
994 static void printNcVersion(void ) {
995 static const char *const ncHelpText = \
996 "nc (NEdit) Version 5.5 (October 2004)\n\n\
997 Built on: %s, %s, %s\n\
998 Built at: %s, %s\n";
1000 fprintf(stdout, ncHelpText,
1001 COMPILE_OS, COMPILE_MACHINE, COMPILE_COMPILER,
1002 __DATE__, __TIME__);