Added OpenMotif 2.2.3 and Lesstif 0.93.94 to the "known good" lists.
[nedit.git] / source / server.c
blobf0367dbb2f1b7e4f6999b8a237de1953fbfcaf58
1 static const char CVSID[] = "$Id: server.c,v 1.30 2004/06/23 13:25:56 edg Exp $";
2 /*******************************************************************************
3 * *
4 * server.c -- Nirvana Editor edit-server component *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * November, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "server.h"
34 #include "textBuf.h"
35 #include "nedit.h"
36 #include "window.h"
37 #include "file.h"
38 #include "selection.h"
39 #include "macro.h"
40 #include "menu.h"
41 #include "preferences.h"
42 #include "server_common.h"
43 #include "../util/fileUtils.h"
44 #include "../util/utils.h"
45 #include "../util/misc.h"
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <limits.h>
51 #ifdef VMS
52 #include <lib$routines.h>
53 #include ssdef
54 #include syidef
55 #include "../util/VMSparam.h"
56 #include "../util/VMSutils.h"
57 #else
58 #include <sys/types.h>
59 #include <sys/utsname.h>
60 #ifndef __MVS__
61 #include <sys/param.h>
62 #endif
63 #include <unistd.h>
64 #include <pwd.h>
65 #endif
67 #include <Xm/Xm.h>
68 #include <Xm/XmP.h>
70 #ifdef HAVE_DEBUG_H
71 #include "../debug.h"
72 #endif
75 static void processServerCommand(void);
76 static void cleanUpServerCommunication(void);
77 static void processServerCommandString(char *string);
78 static void getFileClosedProperty(WindowInfo *window);
79 static int isLocatedOnDesktop(WindowInfo *window, long currentDesktop);
80 static WindowInfo *findWindowOnDesktop(int tabbed, long currentDesktop);
82 static Atom ServerRequestAtom = 0;
83 static Atom ServerExistsAtom = 0;
86 ** Set up inter-client communication for NEdit server end, expected to be
87 ** called only once at startup time
89 void InitServerCommunication(void)
91 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
93 /* Create the server property atoms on the current DISPLAY. */
94 CreateServerPropertyAtoms(GetPrefServerName(),
95 &ServerExistsAtom,
96 &ServerRequestAtom);
98 /* Pay attention to PropertyChangeNotify events on the root window.
99 Do this before putting up the server atoms, to avoid a race
100 condition (when nc sees that the server exists, it sends a command,
101 so we must make sure that we already monitor properties). */
102 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask);
104 /* Create the server-exists property on the root window to tell clients
105 whether to try a request (otherwise clients would always have to
106 try and wait for their timeouts to expire) */
107 XChangeProperty(TheDisplay, rootWindow, ServerExistsAtom, XA_STRING, 8,
108 PropModeReplace, (unsigned char *)"True", 4);
110 /* Set up exit handler for cleaning up server-exists property */
111 atexit(cleanUpServerCommunication);
114 static void deleteProperty(Atom* atom)
116 if (!IsServer) {
117 *atom = None;
118 return;
121 if (*atom != None) {
122 XDeleteProperty(TheDisplay,
123 RootWindow(TheDisplay, DefaultScreen(TheDisplay)),
124 *atom);
125 *atom = None;
130 ** Exit handler. Removes server-exists property on root window
132 static void cleanUpServerCommunication(void)
134 WindowInfo *w;
136 /* Delete any per-file properties that still exist
137 * (and that server knows about)
139 for (w = WindowList; w; w = w->next) {
140 DeleteFileClosedProperty(w);
143 /* Delete any per-file properties that still exist
144 * (but that that server doesn't know about)
146 DeleteServerFileAtoms(GetPrefServerName(),
147 RootWindow(TheDisplay, DefaultScreen(TheDisplay)));
149 /* Delete the server-exists property from the root window (if it was
150 assigned) and don't let the process exit until the X server has
151 processed the delete request (otherwise it won't be done) */
152 deleteProperty(&ServerExistsAtom);
153 XSync(TheDisplay, False);
157 ** Special event loop for NEdit servers. Processes PropertyNotify events on
158 ** the root window (this would not be necessary if it were possible to
159 ** register an Xt event-handler for a window, rather than only a widget).
160 ** Invokes server routines when a server-request property appears,
161 ** re-establishes server-exists property when another server exits and
162 ** this server is still alive to take over.
164 void ServerMainLoop(XtAppContext context)
166 while (TRUE) {
167 XEvent event;
168 XtAppNextEvent(context, &event);
169 ServerDispatchEvent(&event);
173 static void processServerCommand(void)
175 Atom dummyAtom;
176 unsigned long nItems, dummyULong;
177 unsigned char *propValue;
178 int getFmt;
180 /* Get the value of the property, and delete it from the root window */
181 if (XGetWindowProperty(TheDisplay, RootWindow(TheDisplay,
182 DefaultScreen(TheDisplay)), ServerRequestAtom, 0, INT_MAX, True,
183 XA_STRING, &dummyAtom, &getFmt, &nItems, &dummyULong, &propValue)
184 != Success || getFmt != 8)
185 return;
187 /* Invoke the command line processor on the string to process the request */
188 processServerCommandString((char *)propValue);
189 XFree(propValue);
192 Boolean ServerDispatchEvent(XEvent *event)
194 if (IsServer) {
195 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
196 if (event->xany.window == rootWindow && event->xany.type == PropertyNotify) {
197 const XPropertyEvent* e = &event->xproperty;
199 if (e->type == PropertyNotify && e->window == rootWindow) {
200 if (e->atom == ServerRequestAtom && e->state == PropertyNewValue)
201 processServerCommand();
202 else if (e->atom == ServerExistsAtom && e->state == PropertyDelete)
203 XChangeProperty(TheDisplay,
204 rootWindow,
205 ServerExistsAtom, XA_STRING,
206 8, PropModeReplace,
207 (unsigned char *)"True", 4);
211 return XtDispatchEvent(event);
214 /* Try to find existing 'FileOpen' property atom for path. */
215 static Atom findFileOpenProperty(const char* filename,
216 const char* pathname) {
217 char path[MAXPATHLEN];
218 Atom atom;
220 if (!IsServer) return(None);
222 strcpy(path, pathname);
223 strcat(path, filename);
224 atom = CreateServerFileOpenAtom(GetPrefServerName(), path);
225 return(atom);
228 /* Destroy the 'FileOpen' atom to inform nc that this file has
229 ** been opened.
231 static void deleteFileOpenProperty(WindowInfo *window)
233 if (window->filenameSet) {
234 Atom atom = findFileOpenProperty(window->filename, window->path);
235 deleteProperty(&atom);
239 static void deleteFileOpenProperty2(const char* filename,
240 const char* pathname)
242 Atom atom = findFileOpenProperty(filename, pathname);
243 deleteProperty(&atom);
248 /* Try to find existing 'FileClosed' property atom for path. */
249 static Atom findFileClosedProperty(const char* filename,
250 const char* pathname)
252 char path[MAXPATHLEN];
253 Atom atom;
255 if (!IsServer) return(None);
257 strcpy(path, pathname);
258 strcat(path, filename);
259 atom = CreateServerFileClosedAtom(GetPrefServerName(),
260 path,
261 True); /* don't create */
262 return(atom);
265 /* Get hold of the property to use when closing the file. */
266 static void getFileClosedProperty(WindowInfo *window)
268 if (window->filenameSet) {
269 window->fileClosedAtom = findFileClosedProperty(window->filename,
270 window->path);
274 /* Delete the 'FileClosed' atom to inform nc that this file has
275 ** been closed.
277 void DeleteFileClosedProperty(WindowInfo *window)
279 if (window->filenameSet) {
280 deleteProperty(&window->fileClosedAtom);
284 static void deleteFileClosedProperty2(const char* filename,
285 const char* pathname)
287 Atom atom = findFileClosedProperty(filename, pathname);
288 deleteProperty(&atom);
291 static int isLocatedOnDesktop(WindowInfo *window, long currentDesktop)
293 long windowDesktop;
294 if (currentDesktop == -1)
295 return True; /* No desktop information available */
297 windowDesktop = QueryDesktop(TheDisplay, window->shell);
298 /* Sticky windows have desktop 0xFFFFFFFF by convention */
299 if (windowDesktop == currentDesktop || windowDesktop == 0xFFFFFFFFL)
300 return True; /* Desktop matches, or window is sticky */
302 return False;
305 static WindowInfo *findWindowOnDesktop(int tabbed, long currentDesktop)
307 WindowInfo *window;
309 if (currentDesktop == -1) /* No desktop information available */
310 return WindowList;
312 if (tabbed == 0 || (tabbed == -1 && GetPrefOpenInTab() == 0)) {
313 /* A new window is requested, unless we find an untitled unmodified
314 document on the current desktop */
315 for (window=WindowList; window!=NULL; window=window->next) {
316 if (window->filenameSet || window->fileChanged ||
317 window->macroCmdData != NULL)
318 continue;
319 /* No check for top document here! */
320 if (isLocatedOnDesktop(window, currentDesktop))
321 return window;
324 else {
325 /* Find a window on the current desktop to hold the new document */
326 for (window=WindowList; window!=NULL; window=window->next) {
327 /* Avoid unnecessary property access (server round-trip) */
328 if (!IsTopDocument(window))
329 continue;
330 if (isLocatedOnDesktop(window, currentDesktop))
331 return window;
334 return NULL; /* No window found on current desktop -> create new window */
337 static void processServerCommandString(char *string)
339 char *fullname, filename[MAXPATHLEN], pathname[MAXPATHLEN];
340 char *doCommand, *geometry, *langMode, *inPtr;
341 int editFlags, stringLen = strlen(string);
342 int lineNum, createFlag, readFlag, iconicFlag, lastIconic = 0, tabbed;
343 int fileLen, doLen, lmLen, geomLen, charsRead, itemsRead;
344 WindowInfo *window, *lastFile = NULL;
345 long currentDesktop = QueryCurrentDesktop(TheDisplay,
346 RootWindow(TheDisplay, DefaultScreen(TheDisplay)));
348 /* If the command string is empty, put up an empty, Untitled window
349 (or just pop one up if it already exists) */
350 if (string[0] == '\0') {
351 for (window=WindowList; window!=NULL; window=window->next)
352 if (!window->filenameSet && !window->fileChanged &&
353 isLocatedOnDesktop(window, currentDesktop))
354 break;
355 if (window == NULL) {
356 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop), NULL,
357 False, NULL, NULL);
358 CheckCloseDim();
360 else {
361 RaiseDocument(window);
362 XMapRaised(TheDisplay, XtWindow(window->shell));
364 return;
368 ** Loop over all of the files in the command list
370 inPtr = string;
371 while (TRUE) {
373 if (*inPtr == '\0')
374 break;
376 /* Read a server command from the input string. Header contains:
377 linenum createFlag fileLen doLen\n, followed by a filename and -do
378 command both followed by newlines. This bit of code reads the
379 header, and converts the newlines following the filename and do
380 command to nulls to terminate the filename and doCommand strings */
381 itemsRead = sscanf(inPtr, "%d %d %d %d %d %d %d %d %d%n", &lineNum,
382 &readFlag, &createFlag, &iconicFlag, &tabbed, &fileLen,
383 &doLen, &lmLen, &geomLen, &charsRead);
384 if (itemsRead != 9)
385 goto readError;
386 inPtr += charsRead + 1;
387 if (inPtr - string + fileLen > stringLen)
388 goto readError;
389 fullname = inPtr;
390 inPtr += fileLen;
391 *inPtr++ = '\0';
392 if (inPtr - string + doLen > stringLen)
393 goto readError;
394 doCommand = inPtr;
395 inPtr += doLen;
396 *inPtr++ = '\0';
397 if (inPtr - string + lmLen > stringLen)
398 goto readError;
399 langMode = inPtr;
400 inPtr += lmLen;
401 *inPtr++ = '\0';
402 if (inPtr - string + geomLen > stringLen)
403 goto readError;
404 geometry = inPtr;
405 inPtr += geomLen;
406 *inPtr++ = '\0';
408 /* An empty file name means:
409 * put up an empty, Untitled window, or use an existing one
410 * choose a random window for executing the -do macro upon
412 if (fileLen <= 0) {
413 for (window=WindowList; window!=NULL; window=window->next)
414 if (!window->filenameSet && !window->fileChanged &&
415 isLocatedOnDesktop(window, currentDesktop))
416 break;
418 if (*doCommand == '\0') {
419 if (window == NULL) {
420 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop),
421 NULL, iconicFlag, lmLen==0?NULL:langMode, NULL);
422 } else {
423 if (iconicFlag)
424 RaiseDocument(window);
425 else
426 RaiseDocumentWindow(window);
428 } else {
429 WindowInfo *win = WindowList;
430 /* Starting a new command while another one is still running
431 in the same window is not possible (crashes). */
432 while (win != NULL && win->macroCmdData != NULL) {
433 win = win->next;
436 if (!win) {
437 XBell(TheDisplay, 0);
438 } else {
439 /* Raise before -do (macro could close window). */
440 if (iconicFlag)
441 RaiseDocument(win);
442 else
443 RaiseDocumentWindow(win);
444 DoMacro(win, doCommand, "-do macro");
447 CheckCloseDim();
448 return;
451 /* Process the filename by looking for the files in an
452 existing window, or opening if they don't exist */
453 editFlags = (readFlag ? PREF_READ_ONLY : 0) | CREATE |
454 (createFlag ? SUPPRESS_CREATE_WARN : 0);
455 if (ParseFilename(fullname, filename, pathname) != 0) {
456 fprintf(stderr, "NEdit: invalid file name\n");
457 deleteFileClosedProperty2(filename, pathname);
458 break;
461 window = FindWindowWithFile(filename, pathname);
462 if (window == NULL) {
463 /* Files are opened in background to improve opening speed
464 by defering certain time consuiming task such as syntax
465 highlighting. At the end of the file-opening loop, the
466 last file opened will be raised to restore those deferred
467 items. The current file may also be raised if there're
468 macros to execute on. */
469 window = EditExistingFile(findWindowOnDesktop(tabbed, currentDesktop),
470 filename, pathname, editFlags, geometry, iconicFlag,
471 lmLen == 0 ? NULL : langMode,
472 tabbed == -1? GetPrefOpenInTab() : tabbed, True);
474 if (window) {
475 CleanUpTabBarExposeQueue(window);
476 if (lastFile && window->shell != lastFile->shell) {
477 CleanUpTabBarExposeQueue(lastFile);
478 RaiseDocument(lastFile);
484 /* Do the actions requested (note DoMacro is last, since the do
485 command can do anything, including closing the window!) */
486 if (window != NULL) {
487 deleteFileOpenProperty(window);
488 getFileClosedProperty(window);
490 if (lineNum > 0)
491 SelectNumberedLine(window, lineNum);
493 if (*doCommand != '\0') {
494 RaiseDocument(window);
496 if (!iconicFlag)
497 XMapRaised(TheDisplay, XtWindow(window->shell));
499 /* Starting a new command while another one is still running
500 in the same window is not possible (crashes). */
501 if (window->macroCmdData != NULL) {
502 XBell(TheDisplay, 0);
503 } else {
504 DoMacro(window, doCommand, "-do macro");
505 /* in case window is closed by macro functions
506 such as close() or detach_document() */
507 if (!IsValidWindow(window))
508 window = NULL;
509 if (lastFile && !IsValidWindow(lastFile))
510 lastFile = NULL;
514 /* register the last file opened for later use */
515 if (window) {
516 lastFile = window;
517 lastIconic = iconicFlag;
519 } else {
520 deleteFileOpenProperty2(filename, pathname);
521 deleteFileClosedProperty2(filename, pathname);
525 /* Raise the last file opened */
526 if (lastFile) {
527 CleanUpTabBarExposeQueue(lastFile);
528 if (lastIconic)
529 RaiseDocument(lastFile);
530 else
531 RaiseDocumentWindow(lastFile);
532 CheckCloseDim();
534 return;
536 readError:
537 fprintf(stderr, "NEdit: error processing server request\n");
538 return;