/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* */ /**/ #ifdef NON_SOURCE_RELEASE /* WARNING WARNING WARNING! ======================== This file is the same as our remote control reference implementation. That means that it must compile standalone as per the instructions below. That means that this code can't depend on any of the private, internal Netscape libraries: only libc and libX11. That means that if you make any changes to this file, you must also update "http://home.netscape.com/newsref/std/remote.c". Also, tick the minor-minor version number, below. */ #endif /* * remote.c --- remote control of Netscape Navigator for Unix. * version 1.1.3, for Netscape Navigator 1.1 and newer. * * Created: Jamie Zawinski , 24-Dec-94. * * To compile: * * cc -o netscape-remote remote.c -DSTANDALONE -lXmu -lX11 * * To use: * * netscape-remote -help * * Documentation for the protocol which this code implements may be found at: * * http://home.netscape.com/newsref/std/x-remote.html * * Bugs and commentary to x_cbug@netscape.com. */ #include #include #include #include #include #include #include /* for XmuClientWindow() */ /* vroot.h is a header file which lets a client get along with `virtual root' window managers like swm, tvtwm, olvwm, etc. If you don't have this header file, you can find it at "http://home.netscape.com/newsref/std/vroot.h". If you don't care about supporting virtual root window managers, you can comment this line out. */ #include "vroot.h" #ifdef STANDALONE static const char *progname = 0; static const char *expected_mozilla_version = "1.1"; #else /* !STANDALONE */ extern const char *progname; extern const char *expected_mozilla_version; #endif /* !STANDALONE */ #define MOZILLA_VERSION_PROP "_MOZILLA_VERSION" #define MOZILLA_LOCK_PROP "_MOZILLA_LOCK" #define MOZILLA_COMMAND_PROP "_MOZILLA_COMMAND" #define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE" static Atom XA_MOZILLA_VERSION = 0; static Atom XA_MOZILLA_LOCK = 0; static Atom XA_MOZILLA_COMMAND = 0; static Atom XA_MOZILLA_RESPONSE = 0; static void mozilla_remote_init_atoms (Display *dpy) { if (! XA_MOZILLA_VERSION) XA_MOZILLA_VERSION = XInternAtom (dpy, MOZILLA_VERSION_PROP, False); if (! XA_MOZILLA_LOCK) XA_MOZILLA_LOCK = XInternAtom (dpy, MOZILLA_LOCK_PROP, False); if (! XA_MOZILLA_COMMAND) XA_MOZILLA_COMMAND = XInternAtom (dpy, MOZILLA_COMMAND_PROP, False); if (! XA_MOZILLA_RESPONSE) XA_MOZILLA_RESPONSE = XInternAtom (dpy, MOZILLA_RESPONSE_PROP, False); } static Window mozilla_remote_find_window (Display *dpy) { int i; Window root = RootWindowOfScreen (DefaultScreenOfDisplay (dpy)); Window root2, parent, *kids; unsigned int nkids; Window result = 0; Window tenative = 0; unsigned char *tenative_version = 0; if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) { fprintf (stderr, "%s: XQueryTree failed on display %s\n", progname, DisplayString (dpy)); exit (2); } /* root != root2 is possible with virtual root WMs. */ if (! (kids && nkids)) { fprintf (stderr, "%s: root window has no children on display %s\n", progname, DisplayString (dpy)); exit (2); } for (i = nkids-1; i >= 0; i--) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *version = 0; Window w = XmuClientWindow (dpy, kids[i]); int status = XGetWindowProperty (dpy, w, XA_MOZILLA_VERSION, 0, (65536 / sizeof (long)), False, XA_STRING, &type, &format, &nitems, &bytesafter, &version); if (! version) continue; if (strcmp ((char *) version, expected_mozilla_version) && !tenative) { tenative = w; tenative_version = version; continue; } XFree (version); if (status == Success && type != None) { result = w; break; } } if (result && tenative) { fprintf (stderr, "%s: warning: both version %s (0x%x) and version\n" "\t%s (0x%x) are running. Using version %s.\n", progname, tenative_version, (unsigned int) tenative, expected_mozilla_version, (unsigned int) result, expected_mozilla_version); XFree (tenative_version); return result; } else if (tenative) { fprintf (stderr, "%s: warning: expected version %s but found version\n" "\t%s (0x%x) instead.\n", progname, expected_mozilla_version, tenative_version, (unsigned int) tenative); XFree (tenative_version); return tenative; } else if (result) { return result; } else { fprintf (stderr, "%s: not running on display %s\n", progname, DisplayString (dpy)); exit (1); } } static void mozilla_remote_check_window (Display *dpy, Window window) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *version = 0; int status = XGetWindowProperty (dpy, window, XA_MOZILLA_VERSION, 0, (65536 / sizeof (long)), False, XA_STRING, &type, &format, &nitems, &bytesafter, &version); if (status != Success || !version) { fprintf (stderr, "%s: window 0x%x is not a Netscape window.\n", progname, (unsigned int) window); exit (6); } else if (strcmp ((char *) version, expected_mozilla_version)) { fprintf (stderr, "%s: warning: window 0x%x is Netscape version %s;\n" "\texpected version %s.\n", progname, (unsigned int) window, version, expected_mozilla_version); } XFree (version); } static char *lock_data = 0; static void mozilla_remote_obtain_lock (Display *dpy, Window window) { Bool locked = False; Bool waited = False; if (! lock_data) { lock_data = (char *) malloc (255); sprintf (lock_data, "pid%d@", getpid ()); if (gethostname (lock_data + strlen (lock_data), 100)) { perror ("gethostname"); exit (-1); } } do { int result; Atom actual_type; int actual_format; unsigned long nitems, bytes_after; unsigned char *data = 0; XGrabServer (dpy); /* ################################# DANGER! */ result = XGetWindowProperty (dpy, window, XA_MOZILLA_LOCK, 0, (65536 / sizeof (long)), False, /* don't delete */ XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data); if (result != Success || actual_type == None) { /* It's not now locked - lock it. */ #ifdef DEBUG_PROPS fprintf (stderr, "%s: (writing " MOZILLA_LOCK_PROP " \"%s\" to 0x%x)\n", progname, lock_data, (unsigned int) window); #endif XChangeProperty (dpy, window, XA_MOZILLA_LOCK, XA_STRING, 8, PropModeReplace, (unsigned char *) lock_data, strlen (lock_data)); locked = True; } XUngrabServer (dpy); /* ################################# danger over */ XSync (dpy, False); if (! locked) { /* We tried to grab the lock this time, and failed because someone else is holding it already. So, wait for a PropertyDelete event to come in, and try again. */ fprintf (stderr, "%s: window 0x%x is locked by %s; waiting...\n", progname, (unsigned int) window, data); waited = True; while (1) { XEvent event; XNextEvent (dpy, &event); if (event.xany.type == DestroyNotify && event.xdestroywindow.window == window) { fprintf (stderr, "%s: window 0x%x unexpectedly destroyed.\n", progname, (unsigned int) window); exit (6); } else if (event.xany.type == PropertyNotify && event.xproperty.state == PropertyDelete && event.xproperty.window == window && event.xproperty.atom == XA_MOZILLA_LOCK) { /* Ok! Someone deleted their lock, so now we can try again. */ #ifdef DEBUG_PROPS fprintf (stderr, "%s: (0x%x unlocked, trying again...)\n", progname, (unsigned int) window); #endif break; } } } if (data) XFree (data); } while (! locked); if (waited) fprintf (stderr, "%s: obtained lock.\n", progname); } static void mozilla_remote_free_lock (Display *dpy, Window window) { int result; Atom actual_type; int actual_format; unsigned long nitems, bytes_after; unsigned char *data = 0; #ifdef DEBUG_PROPS fprintf (stderr, "%s: (deleting " MOZILLA_LOCK_PROP " \"%s\" from 0x%x)\n", progname, lock_data, (unsigned int) window); #endif result = XGetWindowProperty (dpy, window, XA_MOZILLA_LOCK, 0, (65536 / sizeof (long)), True, /* atomic delete after */ XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data); if (result != Success) { fprintf (stderr, "%s: unable to read and delete " MOZILLA_LOCK_PROP " property\n", progname); return; } else if (!data || !*data) { fprintf (stderr, "%s: invalid data on " MOZILLA_LOCK_PROP " of window 0x%x.\n", progname, (unsigned int) window); return; } else if (strcmp ((char *) data, lock_data)) { fprintf (stderr, "%s: " MOZILLA_LOCK_PROP " was stolen! Expected \"%s\", saw \"%s\"!\n", progname, lock_data, data); return; } if (data) XFree (data); } static int mozilla_remote_command (Display *dpy, Window window, const char *command, Bool raise_p) { int result = 6; Bool done = False; char *new_command = 0; /* The -noraise option is implemented by passing a "noraise" argument to each command to which it should apply. */ if (! raise_p) { char *close; new_command = (char *) malloc (strlen (command) + 20); strcpy (new_command, command); close = strrchr (new_command, ')'); if (close) strcpy (close, ", noraise)"); else strcat (new_command, "(noraise)"); command = new_command; } #ifdef DEBUG_PROPS fprintf (stderr, "%s: (writing " MOZILLA_COMMAND_PROP " \"%s\" to 0x%x)\n", progname, command, (unsigned int) window); #endif XChangeProperty (dpy, window, XA_MOZILLA_COMMAND, XA_STRING, 8, PropModeReplace, (unsigned char *) command, strlen (command)); while (!done) { XEvent event; XNextEvent (dpy, &event); if (event.xany.type == DestroyNotify && event.xdestroywindow.window == window) { /* Print to warn user...*/ fprintf (stderr, "%s: window 0x%x was destroyed.\n", progname, (unsigned int) window); result = 6; goto DONE; } else if (event.xany.type == PropertyNotify && event.xproperty.state == PropertyNewValue && event.xproperty.window == window && event.xproperty.atom == XA_MOZILLA_RESPONSE) { Atom actual_type; int actual_format; unsigned long nitems, bytes_after; unsigned char *data = 0; result = XGetWindowProperty (dpy, window, XA_MOZILLA_RESPONSE, 0, (65536 / sizeof (long)), True, /* atomic delete after */ XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data); #ifdef DEBUG_PROPS if (result == Success && data && *data) { fprintf (stderr, "%s: (server sent " MOZILLA_RESPONSE_PROP " \"%s\" to 0x%x.)\n", progname, data, (unsigned int) window); } #endif if (result != Success) { fprintf (stderr, "%s: failed reading " MOZILLA_RESPONSE_PROP " from window 0x%0x.\n", progname, (unsigned int) window); result = 6; done = True; } else if (!data || strlen((char *) data) < 5) { fprintf (stderr, "%s: invalid data on " MOZILLA_RESPONSE_PROP " property of window 0x%0x.\n", progname, (unsigned int) window); result = 6; done = True; } else if (*data == '1') /* positive preliminary reply */ { fprintf (stderr, "%s: %s\n", progname, data + 4); /* keep going */ done = False; } #if 1 else if (!strncmp ((char *)data, "200", 3)) /* positive completion */ { result = 0; done = True; } #endif else if (*data == '2') /* positive completion */ { fprintf (stderr, "%s: %s\n", progname, data + 4); result = 0; done = True; } else if (*data == '3') /* positive intermediate reply */ { fprintf (stderr, "%s: internal error: " "server wants more information? (%s)\n", progname, data); result = 3; done = True; } else if (*data == '4' || /* transient negative completion */ *data == '5') /* permanent negative completion */ { fprintf (stderr, "%s: %s\n", progname, data + 4); result = (*data - '0'); done = True; } else { fprintf (stderr, "%s: unrecognized " MOZILLA_RESPONSE_PROP " from window 0x%x: %s\n", progname, (unsigned int) window, data); result = 6; done = True; } if (data) XFree (data); } #ifdef DEBUG_PROPS else if (event.xany.type == PropertyNotify && event.xproperty.window == window && event.xproperty.state == PropertyDelete && event.xproperty.atom == XA_MOZILLA_COMMAND) { fprintf (stderr, "%s: (server 0x%x has accepted " MOZILLA_COMMAND_PROP ".)\n", progname, (unsigned int) window); } #endif /* DEBUG_PROPS */ } DONE: if (new_command) free (new_command); return result; } int mozilla_remote_commands (Display *dpy, Window window, char **commands) { Bool raise_p = True; int status = 0; mozilla_remote_init_atoms (dpy); if (window == 0) window = mozilla_remote_find_window (dpy); else mozilla_remote_check_window (dpy, window); XSelectInput (dpy, window, (PropertyChangeMask|StructureNotifyMask)); mozilla_remote_obtain_lock (dpy, window); while (*commands) { if (!strcmp (*commands, "-raise")) raise_p = True; else if (!strcmp (*commands, "-noraise")) raise_p = False; else status = mozilla_remote_command (dpy, window, *commands, raise_p); if (status != 0) break; commands++; } /* When status = 6, it means the window has been destroyed */ /* It is invalid to free the lock when window is destroyed. */ if ( status != 6 ) mozilla_remote_free_lock (dpy, window); return status; } #ifdef STANDALONE static void usage (void) { fprintf (stderr, "usage: %s [ options ... ]\n\ where options include:\n\ \n\ -help to show this message.\n\ -display to specify the X server to use.\n\ -remote to execute a command in an already-running\n\ Netscape process. See the manual for a\n\ list of valid commands.\n\ -id the id of an X window to which the -remote\n\ commands should be sent; if unspecified,\n\ the first window found will be used.\n\ -raise whether following -remote commands should\n\ cause the window to raise itself to the top\n\ (this is the default.)\n\ -noraise the opposite of -raise: following -remote\n\ commands will not auto-raise the window.\n\ ", progname); } void main (int argc, char **argv) { Display *dpy; char *dpy_string = 0; char **remote_commands = 0; int remote_command_count = 0; int remote_command_size = 0; unsigned long remote_window = 0; Bool sync_p = False; int i; progname = strrchr (argv[0], '/'); if (progname) progname++; else progname = argv[0]; /* Hack the -help and -version arguments before opening the display. */ for (i = 1; i < argc; i++) { if (!strcasecmp (argv [i], "-h") || !strcasecmp (argv [i], "-help")) { usage (); exit (0); } else if (!strcmp (argv [i], "-d") || !strcmp (argv [i], "-dpy") || !strcmp (argv [i], "-disp") || !strcmp (argv [i], "-display")) { i++; dpy_string = argv [i]; } else if (!strcmp (argv [i], "-sync") || !strcmp (argv [i], "-synchronize")) { sync_p = True; } else if (!strcmp (argv [i], "-remote")) { if (remote_command_count == remote_command_size) { remote_command_size += 20; remote_commands = (remote_commands ? realloc (remote_commands, remote_command_size * sizeof (char *)) : calloc (remote_command_size, sizeof (char *))); } i++; if (!argv[i] || *argv[i] == '-' || *argv[i] == 0) { fprintf (stderr, "%s: invalid `-remote' option \"%s\"\n", progname, argv[i] ? argv[i] : ""); usage (); exit (-1); } remote_commands [remote_command_count++] = argv[i]; } else if (!strcmp (argv [i], "-raise") || !strcmp (argv [i], "-noraise")) { char *r = argv [i]; if (remote_command_count == remote_command_size) { remote_command_size += 20; remote_commands = (remote_commands ? realloc (remote_commands, remote_command_size * sizeof (char *)) : calloc (remote_command_size, sizeof (char *))); } remote_commands [remote_command_count++] = r; } else if (!strcmp (argv [i], "-id")) { char c; if (remote_command_count > 0) { fprintf (stderr, "%s: the `-id' option must preceed all `-remote' options.\n", progname); usage (); exit (-1); } else if (remote_window != 0) { fprintf (stderr, "%s: only one `-id' option may be used.\n", progname); usage (); exit (-1); } i++; if (argv[i] && 1 == sscanf (argv[i], " %ld %c", &remote_window, &c)) ; else if (argv[i] && 1 == sscanf (argv[i], " 0x%lx %c", &remote_window, &c)) ; else { fprintf (stderr, "%s: invalid `-id' option \"%s\"\n", progname, argv[i] ? argv[i] : ""); usage (); exit (-1); } } } dpy = XOpenDisplay (dpy_string); if (! dpy) exit (-1); if (sync_p) XSynchronize (dpy, True); exit (mozilla_remote_commands (dpy, (Window) remote_window, remote_commands)); } #endif /* STANDALONE */