UNIXworkcode

1 /******************************************************************************* 2 * * 3 * server.c -- Nirvana Editor edit-server component * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute version of this program linked to * 11 * Motif or Open Motif. See README for details. * 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 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 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 "filter.h" 44 #include "../util/fileUtils.h" 45 #include "../util/utils.h" 46 #include "../util/misc.h" 47 #include "../util/nedit_malloc.h" 48 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <limits.h> 53 #include <sys/types.h> 54 #include <sys/utsname.h> 55 #include <sys/param.h> 56 #include <unistd.h> 57 #include <pwd.h> 58 59 #include <Xm/Xm.h> 60 #include <Xm/XmP.h> 61 62 #ifdef HAVE_DEBUG_H 63 #include "../debug.h" 64 #endif 65 66 67 static void processServerCommand(void); 68 static void cleanUpServerCommunication(void); 69 static void processServerCommandString(char *string); 70 static void getFileClosedProperty(WindowInfo *window); 71 static int isLocatedOnDesktop(WindowInfo *window, long currentDesktop); 72 static WindowInfo *findWindowOnDesktop(int tabbed, long currentDesktop); 73 74 static Atom ServerRequestAtom = 0; 75 static Atom ServerExistsAtom = 0; 76 77 /* 78 ** Set up inter-client communication for NEdit server end, expected to be 79 ** called only once at startup time 80 */ 81 void InitServerCommunication(void) 82 { 83 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay)); 84 85 /* Create the server property atoms on the current DISPLAY. */ 86 CreateServerPropertyAtoms(GetPrefServerName(), 87 &ServerExistsAtom, 88 &ServerRequestAtom); 89 90 /* Pay attention to PropertyChangeNotify events on the root window. 91 Do this before putting up the server atoms, to avoid a race 92 condition (when nc sees that the server exists, it sends a command, 93 so we must make sure that we already monitor properties). */ 94 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask); 95 96 /* Create the server-exists property on the root window to tell clients 97 whether to try a request (otherwise clients would always have to 98 try and wait for their timeouts to expire) */ 99 XChangeProperty(TheDisplay, rootWindow, ServerExistsAtom, XA_STRING, 8, 100 PropModeReplace, (unsigned char *)"True", 4); 101 102 /* Set up exit handler for cleaning up server-exists property */ 103 atexit(cleanUpServerCommunication); 104 } 105 106 static void deleteProperty(Atom* atom) 107 { 108 if (!IsServer) { 109 *atom = None; 110 return; 111 } 112 113 if (*atom != None) { 114 XDeleteProperty(TheDisplay, 115 RootWindow(TheDisplay, DefaultScreen(TheDisplay)), 116 *atom); 117 *atom = None; 118 } 119 } 120 121 /* 122 ** Exit handler. Removes server-exists property on root window 123 */ 124 static void cleanUpServerCommunication(void) 125 { 126 WindowInfo *w; 127 128 /* Delete any per-file properties that still exist 129 * (and that server knows about) 130 */ 131 for (w = WindowList; w; w = w->next) { 132 DeleteFileClosedProperty(w); 133 } 134 135 /* Delete any per-file properties that still exist 136 * (but that that server doesn't know about) 137 */ 138 DeleteServerFileAtoms(GetPrefServerName(), 139 RootWindow(TheDisplay, DefaultScreen(TheDisplay))); 140 141 /* Delete the server-exists property from the root window (if it was 142 assigned) and don't let the process exit until the X server has 143 processed the delete request (otherwise it won't be done) */ 144 deleteProperty(&ServerExistsAtom); 145 XSync(TheDisplay, False); 146 } 147 148 /* 149 ** Special event loop for NEdit servers. Processes PropertyNotify events on 150 ** the root window (this would not be necessary if it were possible to 151 ** register an Xt event-handler for a window, rather than only a widget). 152 ** Invokes server routines when a server-request property appears, 153 ** re-establishes server-exists property when another server exits and 154 ** this server is still alive to take over. 155 */ 156 void ServerMainLoop(XtAppContext context) 157 { 158 while (TRUE) { 159 XEvent event; 160 XtAppNextEvent(context, &event); 161 ServerDispatchEvent(&event); 162 } 163 } 164 165 static void processServerCommand(void) 166 { 167 Atom dummyAtom; 168 unsigned long nItems, dummyULong; 169 unsigned char *propValue; 170 int getFmt; 171 172 /* Get the value of the property, and delete it from the root window */ 173 if (XGetWindowProperty(TheDisplay, RootWindow(TheDisplay, 174 DefaultScreen(TheDisplay)), ServerRequestAtom, 0, INT_MAX, True, 175 XA_STRING, &dummyAtom, &getFmt, &nItems, &dummyULong, &propValue) 176 != Success || getFmt != 8) 177 return; 178 179 /* Invoke the command line processor on the string to process the request */ 180 processServerCommandString((char *)propValue); 181 XFree(propValue); 182 } 183 184 Boolean ServerDispatchEvent(XEvent *event) 185 { 186 if (IsServer) { 187 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay)); 188 if (event->xany.window == rootWindow && event->xany.type == PropertyNotify) { 189 const XPropertyEvent* e = &event->xproperty; 190 191 if (e->type == PropertyNotify && e->window == rootWindow) { 192 if (e->atom == ServerRequestAtom && e->state == PropertyNewValue) 193 processServerCommand(); 194 else if (e->atom == ServerExistsAtom && e->state == PropertyDelete) 195 XChangeProperty(TheDisplay, 196 rootWindow, 197 ServerExistsAtom, XA_STRING, 198 8, PropModeReplace, 199 (unsigned char *)"True", 4); 200 } 201 } 202 } 203 return XtDispatchEvent(event); 204 } 205 206 /* Try to find existing 'FileOpen' property atom for path. */ 207 static Atom findFileOpenProperty(const char* filename, 208 const char* pathname) { 209 char path[MAXPATHLEN]; 210 Atom atom; 211 212 if (!IsServer) return(None); 213 214 strcpy(path, pathname); 215 strcat(path, filename); 216 atom = CreateServerFileOpenAtom(GetPrefServerName(), path); 217 return(atom); 218 } 219 220 /* Destroy the 'FileOpen' atom to inform nc that this file has 221 ** been opened. 222 */ 223 static void deleteFileOpenProperty(WindowInfo *window) 224 { 225 if (window->filenameSet) { 226 Atom atom = findFileOpenProperty(window->filename, window->path); 227 deleteProperty(&atom); 228 } 229 } 230 231 static void deleteFileOpenProperty2(const char* filename, 232 const char* pathname) 233 { 234 Atom atom = findFileOpenProperty(filename, pathname); 235 deleteProperty(&atom); 236 } 237 238 239 240 /* Try to find existing 'FileClosed' property atom for path. */ 241 static Atom findFileClosedProperty(const char* filename, 242 const char* pathname) 243 { 244 char path[MAXPATHLEN]; 245 Atom atom; 246 247 if (!IsServer) return(None); 248 249 strcpy(path, pathname); 250 strcat(path, filename); 251 atom = CreateServerFileClosedAtom(GetPrefServerName(), 252 path, 253 True); /* don't create */ 254 return(atom); 255 } 256 257 /* Get hold of the property to use when closing the file. */ 258 static void getFileClosedProperty(WindowInfo *window) 259 { 260 if (window->filenameSet) { 261 window->fileClosedAtom = findFileClosedProperty(window->filename, 262 window->path); 263 } 264 } 265 266 /* Delete the 'FileClosed' atom to inform nc that this file has 267 ** been closed. 268 */ 269 void DeleteFileClosedProperty(WindowInfo *window) 270 { 271 if (window->filenameSet) { 272 deleteProperty(&window->fileClosedAtom); 273 } 274 } 275 276 static void deleteFileClosedProperty2(const char* filename, 277 const char* pathname) 278 { 279 Atom atom = findFileClosedProperty(filename, pathname); 280 deleteProperty(&atom); 281 } 282 283 static int isLocatedOnDesktop(WindowInfo *window, long currentDesktop) 284 { 285 long windowDesktop; 286 if (currentDesktop == -1) 287 return True; /* No desktop information available */ 288 289 windowDesktop = QueryDesktop(TheDisplay, window->shell); 290 /* Sticky windows have desktop 0xFFFFFFFF by convention */ 291 if (windowDesktop == currentDesktop || windowDesktop == 0xFFFFFFFFL) 292 return True; /* Desktop matches, or window is sticky */ 293 294 return False; 295 } 296 297 static WindowInfo *findWindowOnDesktop(int tabbed, long currentDesktop) 298 { 299 WindowInfo *window; 300 301 if (tabbed == 0 || (tabbed == -1 && GetPrefOpenInTab() == 0)) { 302 /* A new window is requested, unless we find an untitled unmodified 303 document on the current desktop */ 304 for (window=WindowList; window!=NULL; window=window->next) { 305 if (window->filenameSet || window->fileChanged || 306 window->macroCmdData != NULL) { 307 continue; 308 } 309 /* No check for top document here! */ 310 if (isLocatedOnDesktop(window, currentDesktop)) { 311 return window; 312 } 313 } 314 } else { 315 /* Find a window on the current desktop to hold the new document */ 316 for (window=WindowList; window!=NULL; window=window->next) { 317 /* Avoid unnecessary property access (server round-trip) */ 318 if (!IsTopDocument(window)) { 319 continue; 320 } 321 if (isLocatedOnDesktop(window, currentDesktop)) { 322 return window; 323 } 324 } 325 } 326 327 return NULL; /* No window found on current desktop -> create new window */ 328 } 329 330 static void processServerCommandString(char *string) 331 { 332 char *fullname, filename[MAXPATHLEN], pathname[MAXPATHLEN]; 333 char *doCommand, *geometry, *langMode, *inPtr; 334 int editFlags, stringLen = strlen(string); 335 int lineNum, createFlag, readFlag, iconicFlag, lastIconic = 0, tabbed = -1; 336 int fileLen, doLen, lmLen, geomLen, charsRead, itemsRead; 337 WindowInfo *window, *lastFile = NULL; 338 long currentDesktop = QueryCurrentDesktop(TheDisplay, 339 RootWindow(TheDisplay, DefaultScreen(TheDisplay))); 340 341 /* If the command string is empty, put up an empty, Untitled window 342 (or just pop one up if it already exists) */ 343 if (string[0] == '\0') { 344 for (window=WindowList; window!=NULL; window=window->next) 345 if (!window->filenameSet && !window->fileChanged && 346 isLocatedOnDesktop(window, currentDesktop)) 347 break; 348 if (window == NULL) { 349 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop), NULL, 350 False, NULL, NULL); 351 CheckCloseDim(); 352 } 353 else { 354 RaiseDocument(window); 355 WmClientMsg(TheDisplay, XtWindow(window->shell), 356 "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0); 357 XMapRaised(TheDisplay, XtWindow(window->shell)); 358 } 359 return; 360 } 361 362 /* 363 ** Loop over all of the files in the command list 364 */ 365 inPtr = string; 366 while (TRUE) { 367 368 if (*inPtr == '\0') 369 break; 370 371 /* Read a server command from the input string. Header contains: 372 linenum createFlag fileLen doLen\n, followed by a filename and -do 373 command both followed by newlines. This bit of code reads the 374 header, and converts the newlines following the filename and do 375 command to nulls to terminate the filename and doCommand strings */ 376 itemsRead = sscanf(inPtr, "%d %d %d %d %d %d %d %d %d%n", &lineNum, 377 &readFlag, &createFlag, &iconicFlag, &tabbed, &fileLen, 378 &doLen, &lmLen, &geomLen, &charsRead); 379 if (itemsRead != 9) 380 goto readError; 381 inPtr += charsRead + 1; 382 if (inPtr - string + fileLen > stringLen) 383 goto readError; 384 fullname = inPtr; 385 inPtr += fileLen; 386 *inPtr++ = '\0'; 387 if (inPtr - string + doLen > stringLen) 388 goto readError; 389 doCommand = inPtr; 390 inPtr += doLen; 391 *inPtr++ = '\0'; 392 if (inPtr - string + lmLen > stringLen) 393 goto readError; 394 langMode = inPtr; 395 inPtr += lmLen; 396 *inPtr++ = '\0'; 397 if (inPtr - string + geomLen > stringLen) 398 goto readError; 399 geometry = inPtr; 400 inPtr += geomLen; 401 *inPtr++ = '\0'; 402 403 /* An empty file name means: 404 * put up an empty, Untitled window, or use an existing one 405 * choose a random window for executing the -do macro upon 406 */ 407 if (fileLen <= 0) { 408 for (window=WindowList; window!=NULL; window=window->next) { 409 if(!window->filenameSet && !window->fileChanged && 410 (isLocatedOnDesktop(window, currentDesktop))) { 411 break; 412 } 413 } 414 415 if (*doCommand == '\0') { 416 if (window == NULL) { 417 EditNewFile(findWindowOnDesktop(tabbed, currentDesktop), 418 NULL, iconicFlag, lmLen==0?NULL:langMode, NULL); 419 } else { 420 if (iconicFlag) 421 RaiseDocument(window); 422 else 423 RaiseDocumentWindow(window); 424 } 425 } else { 426 WindowInfo *win = WindowList; 427 /* Starting a new command while another one is still running 428 in the same window is not possible (crashes). */ 429 while (win != NULL && win->macroCmdData != NULL) { 430 win = win->next; 431 } 432 433 if (!win) { 434 XBell(TheDisplay, 0); 435 } else { 436 /* Raise before -do (macro could close window). */ 437 if (iconicFlag) 438 RaiseDocument(win); 439 else 440 RaiseDocumentWindow(win); 441 DoMacro(win, doCommand, "-do macro"); 442 } 443 } 444 CheckCloseDim(); 445 return; 446 } 447 448 /* Process the filename by looking for the files in an 449 existing window, or opening if they don't exist */ 450 editFlags = (readFlag ? PREF_READ_ONLY : 0) | CREATE | 451 (createFlag ? SUPPRESS_CREATE_WARN : 0); 452 if (ParseFilename(fullname, filename, pathname) != 0) { 453 fprintf(stderr, "XNEdit: invalid file name\n"); 454 deleteFileClosedProperty2(filename, pathname); 455 break; 456 } 457 458 window = FindWindowWithFile(filename, pathname); 459 if (window == NULL) { 460 /* determine filter */ 461 size_t pathlen = strlen(pathname); 462 size_t namelen = strlen(filename); 463 char *fullpath = NEditMalloc(pathlen + namelen + 1); 464 memcpy(fullpath, pathname, pathlen); 465 memcpy(fullpath+pathlen, filename, namelen); 466 fullpath[pathlen+namelen] = '\0'; 467 IOFilter *filter = GetFilterForPath(fullpath); 468 const char *filter_name = filter ? filter->name : NULL; 469 NEditFree(fullpath); 470 471 /* Files are opened in background to improve opening speed 472 by defering certain time consuiming task such as syntax 473 highlighting. At the end of the file-opening loop, the 474 last file opened will be raised to restore those deferred 475 items. The current file may also be raised if there're 476 macros to execute on. */ 477 window = EditExistingFile(findWindowOnDesktop(tabbed, currentDesktop), 478 filename, pathname, NULL, filter_name, editFlags, geometry, iconicFlag, 479 lmLen == 0 ? NULL : langMode, 480 tabbed == -1? GetPrefOpenInTab() : tabbed, True); 481 482 if (window) { 483 CleanUpTabBarExposeQueue(window); 484 if (lastFile && window->shell != lastFile->shell) { 485 CleanUpTabBarExposeQueue(lastFile); 486 RaiseDocument(lastFile); 487 } 488 } 489 490 } 491 492 /* Do the actions requested (note DoMacro is last, since the do 493 command can do anything, including closing the window!) */ 494 if (window != NULL) { 495 deleteFileOpenProperty(window); 496 getFileClosedProperty(window); 497 498 if (lineNum > 0) 499 SelectNumberedLine(window, lineNum); 500 501 if (*doCommand != '\0') { 502 RaiseDocument(window); 503 504 if (!iconicFlag) { 505 WmClientMsg(TheDisplay, XtWindow(window->shell), 506 "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0); 507 XMapRaised(TheDisplay, XtWindow(window->shell)); 508 } 509 510 /* Starting a new command while another one is still running 511 in the same window is not possible (crashes). */ 512 if (window->macroCmdData != NULL) { 513 XBell(TheDisplay, 0); 514 } else { 515 DoMacro(window, doCommand, "-do macro"); 516 /* in case window is closed by macro functions 517 such as close() or detach_document() */ 518 if (!IsValidWindow(window)) 519 window = NULL; 520 if (lastFile && !IsValidWindow(lastFile)) 521 lastFile = NULL; 522 } 523 } 524 525 /* register the last file opened for later use */ 526 if (window) { 527 lastFile = window; 528 lastIconic = iconicFlag; 529 } 530 } else { 531 deleteFileOpenProperty2(filename, pathname); 532 deleteFileClosedProperty2(filename, pathname); 533 } 534 } 535 536 /* Raise the last file opened */ 537 if (lastFile) { 538 CleanUpTabBarExposeQueue(lastFile); 539 if (lastIconic) 540 RaiseDocument(lastFile); 541 else 542 RaiseDocumentWindow(lastFile); 543 CheckCloseDim(); 544 } 545 return; 546 547 readError: 548 fprintf(stderr, "XNEdit: error processing server request\n"); 549 return; 550 } 551