Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / source / server.c
blob61b89fb3a3361444ac2f166d6c4b9a2a70a2971d
1 static const char CVSID[] = "$Id: server.c,v 1.35 2010/07/05 06:23:59 lebert 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 WmClientMsg(TheDisplay, XtWindow(window->shell),
365 "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
366 XMapRaised(TheDisplay, XtWindow(window->shell));
368 return;
372 ** Loop over all of the files in the command list
374 inPtr = string;
375 while (TRUE) {
377 if (*inPtr == '\0')
378 break;
380 /* Read a server command from the input string. Header contains:
381 linenum createFlag fileLen doLen\n, followed by a filename and -do
382 command both followed by newlines. This bit of code reads the
383 header, and converts the newlines following the filename and do
384 command to nulls to terminate the filename and doCommand strings */
385 itemsRead = sscanf(inPtr, "%d %d %d %d %d %d %d %d %d%n", &lineNum,
386 &readFlag, &createFlag, &iconicFlag, &tabbed, &fileLen,
387 &doLen, &lmLen, &geomLen, &charsRead);
388 if (itemsRead != 9)
389 goto readError;
390 inPtr += charsRead + 1;
391 if (inPtr - string + fileLen > stringLen)
392 goto readError;
393 fullname = inPtr;
394 inPtr += fileLen;
395 *inPtr++ = '\0';
396 if (inPtr - string + doLen > stringLen)
397 goto readError;
398 doCommand = inPtr;
399 inPtr += doLen;
400 *inPtr++ = '\0';
401 if (inPtr - string + lmLen > stringLen)
402 goto readError;
403 langMode = inPtr;
404 inPtr += lmLen;
405 *inPtr++ = '\0';
406 if (inPtr - string + geomLen > stringLen)
407 goto readError;
408 geometry = inPtr;
409 inPtr += geomLen;
410 *inPtr++ = '\0';
412 /* An empty file name means:
413 * put up an empty, Untitled window, or use an existing one
414 * choose a random window for executing the -do macro upon
416 if (fileLen <= 0) {
417 for (window=WindowList; window!=NULL; window=window->next)
418 if (!window->filenameSet && !window->fileChanged &&
419 isLocatedOnDesktop(window, currentDesktop))
420 break;
422 if (*doCommand == '\0') {
423 if (window == NULL) {
424 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop),
425 NULL, iconicFlag, lmLen==0?NULL:langMode, NULL);
426 } else {
427 if (iconicFlag)
428 RaiseDocument(window);
429 else
430 RaiseDocumentWindow(window);
432 } else {
433 WindowInfo *win = WindowList;
434 /* Starting a new command while another one is still running
435 in the same window is not possible (crashes). */
436 while (win != NULL && win->macroCmdData != NULL) {
437 win = win->next;
440 if (!win) {
441 XBell(TheDisplay, 0);
442 } else {
443 /* Raise before -do (macro could close window). */
444 if (iconicFlag)
445 RaiseDocument(win);
446 else
447 RaiseDocumentWindow(win);
448 DoMacro(win, doCommand, "-do macro");
451 CheckCloseDim();
452 return;
455 /* Process the filename by looking for the files in an
456 existing window, or opening if they don't exist */
457 editFlags = (readFlag ? PREF_READ_ONLY : 0) | CREATE |
458 (createFlag ? SUPPRESS_CREATE_WARN : 0);
459 if (ParseFilename(fullname, filename, pathname) != 0) {
460 fprintf(stderr, "NEdit: invalid file name\n");
461 deleteFileClosedProperty2(filename, pathname);
462 break;
465 window = FindWindowWithFile(filename, pathname);
466 if (window == NULL) {
467 /* Files are opened in background to improve opening speed
468 by defering certain time consuiming task such as syntax
469 highlighting. At the end of the file-opening loop, the
470 last file opened will be raised to restore those deferred
471 items. The current file may also be raised if there're
472 macros to execute on. */
473 window = EditExistingFile(findWindowOnDesktop(tabbed, currentDesktop),
474 filename, pathname, editFlags, geometry, iconicFlag,
475 lmLen == 0 ? NULL : langMode,
476 tabbed == -1? GetPrefOpenInTab() : tabbed, True);
478 if (window) {
479 CleanUpTabBarExposeQueue(window);
480 if (lastFile && window->shell != lastFile->shell) {
481 CleanUpTabBarExposeQueue(lastFile);
482 RaiseDocument(lastFile);
488 /* Do the actions requested (note DoMacro is last, since the do
489 command can do anything, including closing the window!) */
490 if (window != NULL) {
491 deleteFileOpenProperty(window);
492 getFileClosedProperty(window);
494 if (lineNum > 0)
495 SelectNumberedLine(window, lineNum);
497 if (*doCommand != '\0') {
498 RaiseDocument(window);
500 if (!iconicFlag) {
501 WmClientMsg(TheDisplay, XtWindow(window->shell),
502 "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
503 XMapRaised(TheDisplay, XtWindow(window->shell));
506 /* Starting a new command while another one is still running
507 in the same window is not possible (crashes). */
508 if (window->macroCmdData != NULL) {
509 XBell(TheDisplay, 0);
510 } else {
511 DoMacro(window, doCommand, "-do macro");
512 /* in case window is closed by macro functions
513 such as close() or detach_document() */
514 if (!IsValidWindow(window))
515 window = NULL;
516 if (lastFile && !IsValidWindow(lastFile))
517 lastFile = NULL;
521 /* register the last file opened for later use */
522 if (window) {
523 lastFile = window;
524 lastIconic = iconicFlag;
526 } else {
527 deleteFileOpenProperty2(filename, pathname);
528 deleteFileClosedProperty2(filename, pathname);
532 /* Raise the last file opened */
533 if (lastFile) {
534 CleanUpTabBarExposeQueue(lastFile);
535 if (lastIconic)
536 RaiseDocument(lastFile);
537 else
538 RaiseDocumentWindow(lastFile);
539 CheckCloseDim();
541 return;
543 readError:
544 fprintf(stderr, "NEdit: error processing server request\n");
545 return;