| /* -*- Mode:C; tab-width: 8 -*- |
| * remote.c --- remote control of Netscape Navigator for Unix. |
| * version 1.1.3, for Netscape Navigator 1.1 and newer. |
| * |
| * Copyright © 1996 Netscape Communications Corporation, all rights reserved. |
| * Created: Jamie Zawinski <jwz@netscape.com>, 24-Dec-94. |
| * |
| * Permission to use, copy, modify, distribute, and sell this software and its |
| * documentation for any purpose is hereby granted without fee, provided that |
| * the above copyright notice appear in all copies and that both that |
| * copyright notice and this permission notice appear in supporting |
| * documentation. No representations are made about the suitability of this |
| * software for any purpose. It is provided "as is" without express or |
| * implied warranty. |
| * |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| |
| #include <X11/Xlib.h> |
| #include <X11/Xatom.h> |
| #include <X11/Xmu/WinUtil.h> /* 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, status; |
| unsigned long nitems, bytesafter; |
| unsigned char *version = 0; |
| Window w; |
| |
| w = XmuClientWindow (dpy, kids[i]); |
| 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) |
| { |
| #if 0 |
| 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); |
| #endif |
| 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 = 0; |
| 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: unrecognised " 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 <dpy> to specify the X server to use.\n\ |
| -remote <remote-command> to execute a command in an already-running\n\ |
| Netscape process. See the manual for a\n\ |
| list of valid commands.\n\ |
| -id <window-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 */ |