Amendment to prev commit: We can distinguish these two scenarios by the return
[nedit.git] / source / server.c
bloba6b78a27be1be8460d22bc3e6ae6859b9183febf
1 static const char CVSID[] = "$Id: server.c,v 1.34 2007/12/31 11:12:43 yooden 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. 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.h"
35 #include "textBuf.h"
36 #include "nedit.h"
37 #include "window.h"
38 #include "file.h"
39 #include "selection.h"
40 #include "macro.h"
41 #include "menu.h"
42 #include "preferences.h"
43 #include "server_common.h"
44 #include "../util/fileUtils.h"
45 #include "../util/utils.h"
46 #include "../util/misc.h"
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <limits.h>
52 #ifdef VMS
53 #include <lib$routines.h>
54 #include ssdef
55 #include syidef
56 #include "../util/VMSparam.h"
57 #include "../util/VMSutils.h"
58 #else
59 #include <sys/types.h>
60 #include <sys/utsname.h>
61 #ifndef __MVS__
62 #include <sys/param.h>
63 #endif
64 #include <unistd.h>
65 #include <pwd.h>
66 #endif
68 #include <Xm/Xm.h>
69 #include <Xm/XmP.h>
71 #ifdef HAVE_DEBUG_H
72 #include "../debug.h"
73 #endif
76 static void processServerCommand(void);
77 static void cleanUpServerCommunication(void);
78 static void processServerCommandString(char *string);
79 static void getFileClosedProperty(WindowInfo *window);
80 static int isLocatedOnDesktop(WindowInfo *window, long currentDesktop);
81 static WindowInfo *findWindowOnDesktop(int tabbed, long currentDesktop);
83 static Atom ServerRequestAtom = 0;
84 static Atom ServerExistsAtom = 0;
87 ** Set up inter-client communication for NEdit server end, expected to be
88 ** called only once at startup time
90 void InitServerCommunication(void)
92 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
94 /* Create the server property atoms on the current DISPLAY. */
95 CreateServerPropertyAtoms(GetPrefServerName(),
96 &ServerExistsAtom,
97 &ServerRequestAtom);
99 /* Pay attention to PropertyChangeNotify events on the root window.
100 Do this before putting up the server atoms, to avoid a race
101 condition (when nc sees that the server exists, it sends a command,
102 so we must make sure that we already monitor properties). */
103 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask);
105 /* Create the server-exists property on the root window to tell clients
106 whether to try a request (otherwise clients would always have to
107 try and wait for their timeouts to expire) */
108 XChangeProperty(TheDisplay, rootWindow, ServerExistsAtom, XA_STRING, 8,
109 PropModeReplace, (unsigned char *)"True", 4);
111 /* Set up exit handler for cleaning up server-exists property */
112 atexit(cleanUpServerCommunication);
115 static void deleteProperty(Atom* atom)
117 if (!IsServer) {
118 *atom = None;
119 return;
122 if (*atom != None) {
123 XDeleteProperty(TheDisplay,
124 RootWindow(TheDisplay, DefaultScreen(TheDisplay)),
125 *atom);
126 *atom = None;
131 ** Exit handler. Removes server-exists property on root window
133 static void cleanUpServerCommunication(void)
135 WindowInfo *w;
137 /* Delete any per-file properties that still exist
138 * (and that server knows about)
140 for (w = WindowList; w; w = w->next) {
141 DeleteFileClosedProperty(w);
144 /* Delete any per-file properties that still exist
145 * (but that that server doesn't know about)
147 DeleteServerFileAtoms(GetPrefServerName(),
148 RootWindow(TheDisplay, DefaultScreen(TheDisplay)));
150 /* Delete the server-exists property from the root window (if it was
151 assigned) and don't let the process exit until the X server has
152 processed the delete request (otherwise it won't be done) */
153 deleteProperty(&ServerExistsAtom);
154 XSync(TheDisplay, False);
158 ** Special event loop for NEdit servers. Processes PropertyNotify events on
159 ** the root window (this would not be necessary if it were possible to
160 ** register an Xt event-handler for a window, rather than only a widget).
161 ** Invokes server routines when a server-request property appears,
162 ** re-establishes server-exists property when another server exits and
163 ** this server is still alive to take over.
165 void ServerMainLoop(XtAppContext context)
167 while (TRUE) {
168 XEvent event;
169 XtAppNextEvent(context, &event);
170 ServerDispatchEvent(&event);
174 static void processServerCommand(void)
176 Atom dummyAtom;
177 unsigned long nItems, dummyULong;
178 unsigned char *propValue;
179 int getFmt;
181 /* Get the value of the property, and delete it from the root window */
182 if (XGetWindowProperty(TheDisplay, RootWindow(TheDisplay,
183 DefaultScreen(TheDisplay)), ServerRequestAtom, 0, INT_MAX, True,
184 XA_STRING, &dummyAtom, &getFmt, &nItems, &dummyULong, &propValue)
185 != Success || getFmt != 8)
186 return;
188 /* Invoke the command line processor on the string to process the request */
189 processServerCommandString((char *)propValue);
190 XFree(propValue);
193 Boolean ServerDispatchEvent(XEvent *event)
195 if (IsServer) {
196 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
197 if (event->xany.window == rootWindow && event->xany.type == PropertyNotify) {
198 const XPropertyEvent* e = &event->xproperty;
200 if (e->type == PropertyNotify && e->window == rootWindow) {
201 if (e->atom == ServerRequestAtom && e->state == PropertyNewValue)
202 processServerCommand();
203 else if (e->atom == ServerExistsAtom && e->state == PropertyDelete)
204 XChangeProperty(TheDisplay,
205 rootWindow,
206 ServerExistsAtom, XA_STRING,
207 8, PropModeReplace,
208 (unsigned char *)"True", 4);
212 return XtDispatchEvent(event);
215 /* Try to find existing 'FileOpen' property atom for path. */
216 static Atom findFileOpenProperty(const char* filename,
217 const char* pathname) {
218 char path[MAXPATHLEN];
219 Atom atom;
221 if (!IsServer) return(None);
223 strcpy(path, pathname);
224 strcat(path, filename);
225 atom = CreateServerFileOpenAtom(GetPrefServerName(), path);
226 return(atom);
229 /* Destroy the 'FileOpen' atom to inform nc that this file has
230 ** been opened.
232 static void deleteFileOpenProperty(WindowInfo *window)
234 if (window->filenameSet) {
235 Atom atom = findFileOpenProperty(window->filename, window->path);
236 deleteProperty(&atom);
240 static void deleteFileOpenProperty2(const char* filename,
241 const char* pathname)
243 Atom atom = findFileOpenProperty(filename, pathname);
244 deleteProperty(&atom);
249 /* Try to find existing 'FileClosed' property atom for path. */
250 static Atom findFileClosedProperty(const char* filename,
251 const char* pathname)
253 char path[MAXPATHLEN];
254 Atom atom;
256 if (!IsServer) return(None);
258 strcpy(path, pathname);
259 strcat(path, filename);
260 atom = CreateServerFileClosedAtom(GetPrefServerName(),
261 path,
262 True); /* don't create */
263 return(atom);
266 /* Get hold of the property to use when closing the file. */
267 static void getFileClosedProperty(WindowInfo *window)
269 if (window->filenameSet) {
270 window->fileClosedAtom = findFileClosedProperty(window->filename,
271 window->path);
275 /* Delete the 'FileClosed' atom to inform nc that this file has
276 ** been closed.
278 void DeleteFileClosedProperty(WindowInfo *window)
280 if (window->filenameSet) {
281 deleteProperty(&window->fileClosedAtom);
285 static void deleteFileClosedProperty2(const char* filename,
286 const char* pathname)
288 Atom atom = findFileClosedProperty(filename, pathname);
289 deleteProperty(&atom);
292 static int isLocatedOnDesktop(WindowInfo *window, long currentDesktop)
294 long windowDesktop;
295 if (currentDesktop == -1)
296 return True; /* No desktop information available */
298 windowDesktop = QueryDesktop(TheDisplay, window->shell);
299 /* Sticky windows have desktop 0xFFFFFFFF by convention */
300 if (windowDesktop == currentDesktop || windowDesktop == 0xFFFFFFFFL)
301 return True; /* Desktop matches, or window is sticky */
303 return False;
306 static WindowInfo *findWindowOnDesktop(int tabbed, long currentDesktop)
308 WindowInfo *window;
310 if (tabbed == 0 || (tabbed == -1 && GetPrefOpenInTab() == 0)) {
311 /* A new window is requested, unless we find an untitled unmodified
312 document on the current desktop */
313 for (window=WindowList; window!=NULL; window=window->next) {
314 if (window->filenameSet || window->fileChanged ||
315 window->macroCmdData != NULL) {
316 continue;
318 /* No check for top document here! */
319 if (isLocatedOnDesktop(window, currentDesktop)) {
320 return window;
323 } else {
324 /* Find a window on the current desktop to hold the new document */
325 for (window=WindowList; window!=NULL; window=window->next) {
326 /* Avoid unnecessary property access (server round-trip) */
327 if (!IsTopDocument(window)) {
328 continue;
330 if (isLocatedOnDesktop(window, currentDesktop)) {
331 return window;
336 return NULL; /* No window found on current desktop -> create new window */
339 static void processServerCommandString(char *string)
341 char *fullname, filename[MAXPATHLEN], pathname[MAXPATHLEN];
342 char *doCommand, *geometry, *langMode, *inPtr;
343 int editFlags, stringLen = strlen(string);
344 int lineNum, createFlag, readFlag, iconicFlag, lastIconic = 0, tabbed = -1;
345 int fileLen, doLen, lmLen, geomLen, charsRead, itemsRead;
346 WindowInfo *window, *lastFile = NULL;
347 long currentDesktop = QueryCurrentDesktop(TheDisplay,
348 RootWindow(TheDisplay, DefaultScreen(TheDisplay)));
350 /* If the command string is empty, put up an empty, Untitled window
351 (or just pop one up if it already exists) */
352 if (string[0] == '\0') {
353 for (window=WindowList; window!=NULL; window=window->next)
354 if (!window->filenameSet && !window->fileChanged &&
355 isLocatedOnDesktop(window, currentDesktop))
356 break;
357 if (window == NULL) {
358 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop), NULL,
359 False, NULL, NULL);
360 CheckCloseDim();
362 else {
363 RaiseDocument(window);
364 XMapRaised(TheDisplay, XtWindow(window->shell));
366 return;
370 ** Loop over all of the files in the command list
372 inPtr = string;
373 while (TRUE) {
375 if (*inPtr == '\0')
376 break;
378 /* Read a server command from the input string. Header contains:
379 linenum createFlag fileLen doLen\n, followed by a filename and -do
380 command both followed by newlines. This bit of code reads the
381 header, and converts the newlines following the filename and do
382 command to nulls to terminate the filename and doCommand strings */
383 itemsRead = sscanf(inPtr, "%d %d %d %d %d %d %d %d %d%n", &lineNum,
384 &readFlag, &createFlag, &iconicFlag, &tabbed, &fileLen,
385 &doLen, &lmLen, &geomLen, &charsRead);
386 if (itemsRead != 9)
387 goto readError;
388 inPtr += charsRead + 1;
389 if (inPtr - string + fileLen > stringLen)
390 goto readError;
391 fullname = inPtr;
392 inPtr += fileLen;
393 *inPtr++ = '\0';
394 if (inPtr - string + doLen > stringLen)
395 goto readError;
396 doCommand = inPtr;
397 inPtr += doLen;
398 *inPtr++ = '\0';
399 if (inPtr - string + lmLen > stringLen)
400 goto readError;
401 langMode = inPtr;
402 inPtr += lmLen;
403 *inPtr++ = '\0';
404 if (inPtr - string + geomLen > stringLen)
405 goto readError;
406 geometry = inPtr;
407 inPtr += geomLen;
408 *inPtr++ = '\0';
410 /* An empty file name means:
411 * put up an empty, Untitled window, or use an existing one
412 * choose a random window for executing the -do macro upon
414 if (fileLen <= 0) {
415 for (window=WindowList; window!=NULL; window=window->next)
416 if (!window->filenameSet && !window->fileChanged &&
417 isLocatedOnDesktop(window, currentDesktop))
418 break;
420 if (*doCommand == '\0') {
421 if (window == NULL) {
422 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop),
423 NULL, iconicFlag, lmLen==0?NULL:langMode, NULL);
424 } else {
425 if (iconicFlag)
426 RaiseDocument(window);
427 else
428 RaiseDocumentWindow(window);
430 } else {
431 WindowInfo *win = WindowList;
432 /* Starting a new command while another one is still running
433 in the same window is not possible (crashes). */
434 while (win != NULL && win->macroCmdData != NULL) {
435 win = win->next;
438 if (!win) {
439 XBell(TheDisplay, 0);
440 } else {
441 /* Raise before -do (macro could close window). */
442 if (iconicFlag)
443 RaiseDocument(win);
444 else
445 RaiseDocumentWindow(win);
446 DoMacro(win, doCommand, "-do macro");
449 CheckCloseDim();
450 return;
453 /* Process the filename by looking for the files in an
454 existing window, or opening if they don't exist */
455 editFlags = (readFlag ? PREF_READ_ONLY : 0) | CREATE |
456 (createFlag ? SUPPRESS_CREATE_WARN : 0);
457 if (ParseFilename(fullname, filename, pathname) != 0) {
458 fprintf(stderr, "NEdit: invalid file name\n");
459 deleteFileClosedProperty2(filename, pathname);
460 break;
463 window = FindWindowWithFile(filename, pathname);
464 if (window == NULL) {
465 /* Files are opened in background to improve opening speed
466 by defering certain time consuiming task such as syntax
467 highlighting. At the end of the file-opening loop, the
468 last file opened will be raised to restore those deferred
469 items. The current file may also be raised if there're
470 macros to execute on. */
471 window = EditExistingFile(findWindowOnDesktop(tabbed, currentDesktop),
472 filename, pathname, editFlags, geometry, iconicFlag,
473 lmLen == 0 ? NULL : langMode,
474 tabbed == -1? GetPrefOpenInTab() : tabbed, True);
476 if (window) {
477 CleanUpTabBarExposeQueue(window);
478 if (lastFile && window->shell != lastFile->shell) {
479 CleanUpTabBarExposeQueue(lastFile);
480 RaiseDocument(lastFile);
486 /* Do the actions requested (note DoMacro is last, since the do
487 command can do anything, including closing the window!) */
488 if (window != NULL) {
489 deleteFileOpenProperty(window);
490 getFileClosedProperty(window);
492 if (lineNum > 0)
493 SelectNumberedLine(window, lineNum);
495 if (*doCommand != '\0') {
496 RaiseDocument(window);
498 if (!iconicFlag)
499 XMapRaised(TheDisplay, XtWindow(window->shell));
501 /* Starting a new command while another one is still running
502 in the same window is not possible (crashes). */
503 if (window->macroCmdData != NULL) {
504 XBell(TheDisplay, 0);
505 } else {
506 DoMacro(window, doCommand, "-do macro");
507 /* in case window is closed by macro functions
508 such as close() or detach_document() */
509 if (!IsValidWindow(window))
510 window = NULL;
511 if (lastFile && !IsValidWindow(lastFile))
512 lastFile = NULL;
516 /* register the last file opened for later use */
517 if (window) {
518 lastFile = window;
519 lastIconic = iconicFlag;
521 } else {
522 deleteFileOpenProperty2(filename, pathname);
523 deleteFileClosedProperty2(filename, pathname);
527 /* Raise the last file opened */
528 if (lastFile) {
529 CleanUpTabBarExposeQueue(lastFile);
530 if (lastIconic)
531 RaiseDocument(lastFile);
532 else
533 RaiseDocumentWindow(lastFile);
534 CheckCloseDim();
536 return;
538 readError:
539 fprintf(stderr, "NEdit: error processing server request\n");
540 return;