view src/browser.c @ 4617:858979ab3867

[gaim-migrate @ 4908] Big Changes: -Rewrote some of the perl stuff so perl scripts can change a few of their parameters -Receiving a file with AIM over oscar works pretty well Now, the "nitty gritty": Very minor change to prefs.c: In the plugins details tab, I changed "URL" to "Web Site." I was just going to fix the tabbing, but silvestrij suggested changing it to "Web site," and I thought that sounded good. I think it fits better, too. I dunno, maybe that's just me. "Get Capabilities" has stopped working for some reason. I'm just going to blame AOL. It's really not important anyway, and some people wanted it taken off. It is now #ifdef 0'ed out. I'll remove it completely if it continues to no longer function. I took out a few plugin_event calls from oscar.c and put them in core code. "event_error" should be, uh, "evented" when there is an error signing on. Hopefully no one was using this. It's really pretty useless. The parameter is now the reason for not being able to connect rather than the archaic toc error code. I screwed around with how perl functions are called some. There was way the hell too much malloc'ing going on here. I think all in all it's an improvement, though I'm still not a big fan of how changes to parameters propagate to the actual memory. I really think it would be nice if the perl stuff was made into a C plugin. It's just so much cleaner. Especially if someone wanted to write, say, a python or tcl interpreter. That's how xchat2 does it. I just think that would be really slick. Like butter. Or ice. Very unlike Velcro. I added a "Change Password" Protocol Action for ICQ over oscar. This was really pretty easy. I'd like to thank my housemate Andrew for complaining a lot that having to use Windows ICQ to change his password was a pain. I rewrote a lot of the oscar file transfer stuff to use Christian's new xfer interface. This involved moving a few functions from ft.c to im.c, where they belong. I also removed all the #if 0'ed getfile functions. I'll be rewritting them soonish. Receiving a file should work perfectly, aside from maybe a small memleak when stuff is canceled. Sending a file is currently disabled. No ETA on when I'll have that working. I renamed pretty much all of the functions in im.c so they have kind of a scheme now. They should all be aim_im_bleh, since "im" is the family name. There comes a time when you must break the crap out of any clients that might be using libfaim in order to make stuff cleaner. Maybe. I got rid of the snac destructor stuff for now. I'll probably add it back later. I wasn't entirely comfortable with how it was done. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Wed, 26 Feb 2003 05:01:37 +0000
parents 3fef2d45dce0
children 1bf59e3ac4ff
line wrap: on
line source

/*
 * gaim
 *
 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 *
 * some code: (most in this file)
 * Copyright (C) 1996 Netscape Communications Corporation, all rights reserved.
 * Created: Jamie Zawinski <jwz@netscape.com>, 24-Dec-94.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * This code is mainly taken from Netscape's sample implementation of
 * their protocol.  Nifty.
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef _WIN32
#include <gdk/gdkwin32.h>
#else
#include <unistd.h>
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 255
#endif
#include <gdk/gdkx.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>




#include <gtk/gtk.h>
#include <gdk/gdkprivate.h>
#include "gaim.h"

#ifndef _WIN32




#include <X11/Xlib.h>
#include <X11/Xatom.h>


static const char *progname = "gaim";
static const char *expected_mozilla_version = "1.1";

#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 GdkAtom GDKA_MOZILLA_VERSION = 0;
static GdkAtom GDKA_MOZILLA_LOCK = 0;
static GdkAtom GDKA_MOZILLA_COMMAND = 0;
static GdkAtom GDKA_MOZILLA_RESPONSE = 0;

static char *window_check_mozilla_version(Window);
static const char *get_lock_data();
static GdkFilterReturn netscape_response_cb(XEvent *, GdkEvent *, GdkWindow *);
static gboolean netscape_command(const char *);

static int netscape_lock;


static Window VirtualRootWindowOfScreen(screen)
Screen *screen;
{
	static Screen *save_screen = (Screen *) 0;
	static Window root = (Window) 0;

	if (screen != save_screen) {
		Display *dpy = DisplayOfScreen(screen);
		Atom __SWM_VROOT = None;
		unsigned int i;
		Window rootReturn, parentReturn, *children;
		unsigned int numChildren;

		root = RootWindowOfScreen(screen);

		/* go look for a virtual root */
		__SWM_VROOT = XInternAtom(dpy, "__SWM_VROOT", False);
		if (XQueryTree(dpy, root, &rootReturn, &parentReturn, &children, &numChildren)) {
			for (i = 0; i < numChildren; i++) {
				Atom actual_type;
				int actual_format;
				unsigned long nitems, bytesafter;
				Window *newRoot = (Window *) 0;

				if (XGetWindowProperty(dpy, children[i],
						       __SWM_VROOT, 0, 1, False, XA_WINDOW,
						       &actual_type, &actual_format,
						       &nitems, &bytesafter,
						       (unsigned char **)&newRoot) == Success
				    && newRoot) {
					root = *newRoot;
					break;
				}
			}
			if (children)
				XFree((char *)children);
		}

		save_screen = screen;
	}

	return root;
}

/* The following code is Copyright (C) 1989 X Consortium */

static Window TryChildren();

/* Find a window with WM_STATE, else return win itself, as per ICCCM */

static Window GClientWindow(dpy, win)
Display *dpy;
Window win;
{
	Atom WM_STATE;
	Atom type = None;
	int format;
	unsigned long nitems, after;
	unsigned char *data;
	Window inf;

	WM_STATE = XInternAtom(dpy, "WM_STATE", True);
	if (!WM_STATE)
		return win;
	XGetWindowProperty(dpy, win, WM_STATE, 0, 0, False, AnyPropertyType,
			   &type, &format, &nitems, &after, &data);
	if (type) {
		XFree(data);
		return win;
	}

	inf = TryChildren(dpy, win, WM_STATE);
	if (!inf)
		inf = win;

	XFree(data);

	return inf;
}

static
Window TryChildren(dpy, win, WM_STATE)
Display *dpy;
Window win;
Atom WM_STATE;
{
	Window root, parent;
	Window *children;
	unsigned int nchildren;
	unsigned int i;
	Atom type = None;
	int format;
	unsigned long nitems, after;
	unsigned char *data;
	Window inf = 0;

	if (!XQueryTree(dpy, win, &root, &parent, &children, &nchildren))
		return 0;
	for (i = 0; !inf && (i < nchildren); i++) {
		XGetWindowProperty(dpy, children[i], WM_STATE, 0, 0, False,
				   AnyPropertyType, &type, &format, &nitems, &after, &data);
		if (type)
			inf = children[i];

		XFree(data);
	}
	for (i = 0; !inf && (i < nchildren); i++)
		inf = TryChildren(dpy, children[i], WM_STATE);
	if (children)
		XFree((char *)children);
	return inf;
}

/* END X Consortium code */



static void mozilla_remote_init_atoms()
{
	if (!GDKA_MOZILLA_VERSION)
		GDKA_MOZILLA_VERSION = gdk_atom_intern(MOZILLA_VERSION_PROP, 0);
	if (!GDKA_MOZILLA_LOCK)
		GDKA_MOZILLA_LOCK = gdk_atom_intern(MOZILLA_LOCK_PROP, 0);
	if (!GDKA_MOZILLA_COMMAND)
		GDKA_MOZILLA_COMMAND = gdk_atom_intern(MOZILLA_COMMAND_PROP, 0);
	if (!GDKA_MOZILLA_RESPONSE)
		GDKA_MOZILLA_RESPONSE = gdk_atom_intern(MOZILLA_RESPONSE_PROP, 0);
}

static char *window_check_mozilla_version(Window window) {

	Atom type;
	int format;
	unsigned long nitems, bytesafter;
	unsigned char *version = 0;
	gchar *retval = NULL;

	if (XGetWindowProperty(gdk_display, window, 
					gdk_x11_atom_to_xatom(GDKA_MOZILLA_VERSION),
					0, (65536 / sizeof(long)),
					False, XA_STRING,
					&type, &format, &nitems, &bytesafter,
					&version) != Success) {	
		return NULL;
	}

	if (!version) {
		return NULL;
	}

	retval = g_strdup(version);
	XFree(version);

	return retval;
}

static GdkWindow *mozilla_remote_find_window()
{
	int i;
	Window root = VirtualRootWindowOfScreen(DefaultScreenOfDisplay(gdk_display));
	Window root2, parent, *kids;
	unsigned int nkids;
	Window result = 0;
	Window tenative = 0;
	unsigned char *tenative_version = 0, *version = 0;
	static GdkWindow *remote_window = NULL;

	if (remote_window != NULL) {
		version = window_check_mozilla_version(GDK_WINDOW_XID(remote_window));

		if (version != NULL) {
			g_free(version);
			return remote_window;
		}
		g_free(version);

		gdk_window_destroy(remote_window);
		remote_window = NULL;
	}

	if (!XQueryTree(gdk_display, root, &root2, &parent, &kids, &nkids)) {
		debug_printf("%s: XQueryTree failed on display %s\n", progname,
			     DisplayString(gdk_display));
		return NULL;
	}

	/* root != root2 is possible with virtual root WMs. */

	if (!(kids && nkids)) {
		debug_printf("%s: root window has no children on display %s\n",
			     progname, DisplayString(gdk_display));
		return NULL;
	}

	for (i = nkids - 1; i >= 0; i--) {
		Window w = GClientWindow(gdk_display, kids[i]);

        version = window_check_mozilla_version(w);

		if (version == NULL) {
			continue;
		}

		if (strcmp((char *)version, expected_mozilla_version) && !tenative) {
			tenative = w;
			tenative_version = version;
			continue;
		}

		g_free(version);

			result = w;
			break;
		}

	XFree(kids);

	if (result && tenative) {
		debug_printf("%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 gdk_window_foreign_new(result);
	} else if (tenative) {
		debug_printf("%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 gdk_window_foreign_new(tenative);
	} else if (result) {
		return gdk_window_foreign_new(result);
	} else {
		debug_printf("%s: not running on display %s\n", progname, DisplayString(gdk_display));
		return NULL;
	}
}


static const char *get_lock_data() {
	static char *lock_data = NULL;

	if (lock_data == NULL) {
		char hostname[HOST_NAME_MAX + 1] = {0}; 

		if (gethostname(hostname, HOST_NAME_MAX + 1) == 0) {
			lock_data = g_strdup_printf("pid%d@%s", getpid(), hostname);
		} else {
			lock_data = g_strdup_printf("pid%d", getpid());
		}
	}

	return lock_data;
}

static void mozilla_remote_obtain_lock(GdkWindow * window)
{
	gboolean locked = False;
	const char *lock_data = get_lock_data();
		GdkAtom actual_type;
		gint actual_format;
		gint nitems;
		unsigned char *data = 0;

	gdk_x11_grab_server();
	if (!gdk_property_get(window, GDKA_MOZILLA_LOCK,
					  gdk_x11_xatom_to_atom (XA_STRING), 0,
					  (65536 / sizeof(long)), 0,
				  &actual_type, &actual_format, &nitems, &data)) {

			/* It's not now locked - lock it. */
		debug_printf("%s: (writing " MOZILLA_LOCK_PROP " \"%s\" to 0x%x)\n", 
					 progname, lock_data, (unsigned int) window);

			gdk_property_change(window, GDKA_MOZILLA_LOCK, 
					    gdk_x11_xatom_to_atom (XA_STRING),
					    8, PropModeReplace,
					    (unsigned char *)lock_data, strlen(lock_data));
			locked = True;
		}

			if (data)
				g_free(data);
	gdk_x11_ungrab_server();
}


static void mozilla_remote_free_lock(GdkWindow * window)
{
	int result = 0;
	GdkAtom actual_type;
	gint actual_format; 
	gint nitems;
	unsigned char *data = 0;
	const char *lock_data = get_lock_data();

	debug_printf("%s: (deleting " MOZILLA_LOCK_PROP
		     " \"%s\" from 0x%x)\n", progname, lock_data, (unsigned int)window);

	result = gdk_property_get(window, GDKA_MOZILLA_LOCK, 
				  gdk_x11_xatom_to_atom (XA_STRING),
				  0, (65536 / sizeof(long)),
				  1, &actual_type, &actual_format, &nitems, &data);

	if (result != TRUE) {
		debug_printf("%s: unable to read and delete " MOZILLA_LOCK_PROP " property\n", progname);
		return;
	} else if (!data || !*data) {
		debug_printf("%s: invalid data on " MOZILLA_LOCK_PROP
			     " of window 0x%x.\n", progname, (unsigned int)window);
		return;
	} else if (strcmp((char *)data, lock_data)) {
		debug_printf("%s: " MOZILLA_LOCK_PROP
			     " was stolen!  Expected \"%s\", saw \"%s\"!\n", progname, lock_data, data);
		return;
	}

	XFree(data);
}

static GdkFilterReturn netscape_response_cb(XEvent *event, GdkEvent *translated, GdkWindow *window)
{
	Atom actual_type, mozilla_response;
	Window xid;
	int actual_format;
	unsigned long nitems, bytes_after;
	unsigned char *data = 0;

	if (window == NULL || GDK_WINDOW_OBJECT(window)->destroyed) {
		do_error_dialog(_("Communication with the browser failed.  Please close all "
					      "windows and try again."), NULL, GAIM_ERROR);
		debug_printf("netscape_response_cb called with NULL window.\n");
		return GDK_FILTER_CONTINUE;
	}

	mozilla_response = gdk_x11_atom_to_xatom(GDKA_MOZILLA_RESPONSE);
    xid = GDK_WINDOW_XID(window);

	/* If the event isn't what we want then let gtk handle it */
	if (event->xany.type != PropertyNotify ||
		event->xproperty.state != PropertyNewValue ||
		event->xproperty.window != xid ||
		event->xproperty.atom != mozilla_response) {
		return GDK_FILTER_CONTINUE;
	}

	if (XGetWindowProperty (gdk_display, xid, mozilla_response,
							 0, (65536 / sizeof (long)),
							 True, 
							 XA_STRING, 
							 &actual_type, &actual_format,
							 &nitems, &bytes_after,
							 &data) != Success 
		|| data == NULL || (data[0] != '1' && data[0] != '2')) {

		do_error_dialog(_("Communication with the browser failed.  Please close all "
					      "windows and try again."), NULL, GAIM_ERROR);
	} 

    if (data[0] == '1') {
		/* Netscape isn't ready yet */
		debug_printf("Remote Netscape window isn't ready yet.\n");
		return GDK_FILTER_REMOVE;
	} 
	
	if (data[0] == '2') {
		/* Yay! It worked */ 
		debug_printf("Successfully sent command to remote Netscape window.\n");
	}

	gdk_window_remove_filter(window, (GdkFilterFunc) netscape_response_cb, window); 
	mozilla_remote_free_lock(window);
	netscape_lock = 0;
	return GDK_FILTER_REMOVE;
}

static void mozilla_remote_command(GdkWindow * window, const char *command, Bool raise_p)
{
	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 = g_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;
	}

	debug_printf("%s: (writing " MOZILLA_COMMAND_PROP " \"%s\" to 0x%x)\n",
		     progname, command, (unsigned int)window);

	gdk_property_change(window, GDKA_MOZILLA_COMMAND, 
			    gdk_x11_xatom_to_atom (XA_STRING), 
			    8, GDK_PROP_MODE_REPLACE, (unsigned char *)command, strlen(command));

	gdk_window_add_filter(window, (GdkFilterFunc) netscape_response_cb, window);
}

static gboolean netscape_command(const char *command)
{
 	GdkWindow *window = NULL;
 
 	if (netscape_lock) {
 		debug_printf("netscape_command() is currently in use.\n");
		return FALSE;
	}

	netscape_lock = 1;

	mozilla_remote_init_atoms();
	window = mozilla_remote_find_window();

	if (window == NULL || (GDK_WINDOW_OBJECT(window)->destroyed == TRUE)) {
 		debug_printf("Remote window absent or unsuitable.\n");
		netscape_lock = 0;
		return FALSE;
	}

	mozilla_remote_command(window, command, False);

	netscape_lock = 0;
	return TRUE;
}

void open_url(GtkWidget *w, char *url)
{
	char *command = NULL;
	GError *error = NULL;

	switch (web_browser) {
		case BROWSER_NETSCAPE: {
			char *args = NULL;

			if (misc_options & OPT_MISC_BROWSER_POPUP)
				args = g_strdup_printf("OpenURL(%s, new-window)", url);
			else
				args = g_strdup_printf("OpenURL(%s)", url);

			if (netscape_command(args)) {
				g_free(args);
				return;
			}

			/* if netscape is running ...
			command = g_strdup_printf("netscape -remote %s", args); */

			command = g_strdup_printf("netscape \"%s\"", url);
			g_free(args);
			} break;

		case BROWSER_OPERA:
			if (misc_options & OPT_MISC_BROWSER_POPUP)
				command = g_strdup_printf("opera -newwindow \"%s\"", url);
			else
				command = g_strdup_printf("opera \"%s\"", url);
			break;

		case BROWSER_KONQ:
			command = g_strdup_printf("kfmclient openURL \"%s\"", url);
			break;

		case BROWSER_GALEON:
			if (misc_options & OPT_MISC_BROWSER_POPUP)
				command = g_strdup_printf("galeon -w \"%s\"", url);
			else
				command = g_strdup_printf("galeon \"%s\"", url);
			break;

		case BROWSER_MOZILLA:
			command = g_strdup_printf("mozilla \"%s\"", url);
			break;

		case BROWSER_MANUAL: {
			char *space_free_url = NULL;

			if (!web_command[0]) {
				do_error_dialog(_("Unable to launch your browser because the 'Manual' browser command has been chosen, but no command has been set."), NULL, GAIM_ERROR);
				return;
			}

			space_free_url = g_strdup(url);
			g_strdelimit(space_free_url, " ", '+');
			command = g_strdup_printf("%s %s", web_command, space_free_url);
 			g_free(space_free_url);
 			} break;
	}

	if (g_spawn_command_line_async(command, &error) == FALSE) {
		char *tmp = g_strdup_printf(_("There was an error launching your chosen browser: %s"), error->message);
		do_error_dialog(tmp, NULL, GAIM_ERROR);
		g_free(tmp);
		g_error_free(error);
 	}

	g_free(command);
}

void add_bookmark(GtkWidget *w, char *url)
{
	if (web_browser == BROWSER_NETSCAPE) {
		char *command = g_strdup_printf("AddBookmark(%s)", url);

		netscape_command(command);
		g_free(command);
	}
}

#else

/* Sooner or later, I shall support Windows clicking! */

void add_bookmark(GtkWidget *w, char *url)
{
}
void open_url(GtkWidget *w, char *url)
{
	ShellExecute(NULL, NULL, url, NULL, ".\\", 0);
}


#endif /* _WIN32 */