[PackageKit-commit] packagekit: Branch 'master' - 11 commits

Richard Hughes hughsient at kemper.freedesktop.org
Wed Jul 15 03:36:58 PDT 2009


 backends/portage/portageBackend.py          |  156 +---
 backends/yum/yumBackend.py                  |   73 --
 contrib/browser-plugin/Makefile.am          |   21 
 contrib/browser-plugin/README               |   12 
 contrib/browser-plugin/pk-main.c            |  471 +++++++++++++
 contrib/browser-plugin/pk-main.h            |   51 +
 contrib/browser-plugin/pk-plugin-install.c  | 1004 ++++++++++++++++++++++++++++
 contrib/browser-plugin/pk-plugin-install.h  |   59 +
 contrib/browser-plugin/pk-plugin.c          |  431 ++++++++++++
 contrib/browser-plugin/pk-plugin.h          |  105 ++
 contrib/browser-plugin/pk-store.c           |  175 ++++
 contrib/browser-plugin/pk-store.h           |   64 +
 contrib/browser-plugin/sdk/npplat.h         |   64 -
 contrib/browser-plugin/sdk/pluginbase.h     |   96 --
 contrib/browser-plugin/src/contents.cpp     |  798 ----------------------
 contrib/browser-plugin/src/contents.h       |  126 ---
 contrib/browser-plugin/src/plugin.cpp       |  296 --------
 contrib/browser-plugin/src/plugin.h         |   81 --
 docs/security.txt                           |   49 -
 etc/PackageKit.conf.in                      |   23 
 po/POTFILES.in                              |    2 
 policy/org.freedesktop.packagekit.policy.in |   63 +
 src/Makefile.am                             |    1 
 src/pk-transaction-list.c                   |   19 
 24 files changed, 2570 insertions(+), 1670 deletions(-)

New commits:
commit 8831b68d8884d1cee81e29e4dbdd48a74f8d7aac
Author: Richard Hughes <richard at hughsie.com>
Date:   Wed Jul 15 10:44:12 2009 +0100

    yum: Ignore another error in checkForNewer. Fixes rh#510874

diff --git a/backends/yum/yumBackend.py b/backends/yum/yumBackend.py
index b894264..962229f 100755
--- a/backends/yum/yumBackend.py
+++ b/backends/yum/yumBackend.py
@@ -1524,6 +1524,8 @@ class PackageKitYumBackend(PackageKitBaseBackend, PackagekitPackage):
             pkgs = self.yumbase.pkgSack.returnNewestByName(name=po.name)
         except yum.Errors.PackageSackError:
             pass
+        except yum.Errors.RepoError, e:
+            pass
         except Exception, e:
             self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
         if pkgs:
commit 00bef85440b91a8aa76dae694fb789be2e8be7c9
Author: Richard Hughes <richard at hughsie.com>
Date:   Wed Jul 15 10:41:15 2009 +0100

    yum: remove the preupgrade code, as we now depend on this package

diff --git a/backends/yum/yumBackend.py b/backends/yum/yumBackend.py
index 6ec5bd1..b894264 100755
--- a/backends/yum/yumBackend.py
+++ b/backends/yum/yumBackend.py
@@ -2057,77 +2057,6 @@ class PackageKitYumBackend(PackageKitBaseBackend, PackagekitPackage):
         self.percentage(None)
         self.status(STATUS_QUERY)
 
-        # is preupgrade installed?
-        try:
-            pkgs = self.yumbase.rpmdb.searchNevra(name='preupgrade')
-        except Exception, e:
-            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
-        if len(pkgs) == 0:
-
-            # find preupgrade, which may not exist
-            try:
-                pkgs = self.yumbase.pkgSack.returnNewestByName(name='preupgrade')
-            except yum.Errors.PackageSackError, e:
-                self.error(ERROR_NO_DISTRO_UPGRADE_DATA, "Could not find preupgrade package in any enabled repos")
-                return
-            except yum.Errors.RepoError, e:
-                self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
-                return
-            except Exception, e:
-                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
-                return
-
-            # shouldn't happen
-            if len(pkgs) == 0:
-                self.error(ERROR_NO_DISTRO_UPGRADE_DATA, "Could not find preupgrade package in any enabled repos")
-                return
-
-            # we can have more than one result if the package is in multiple repos, for example
-            # a machine with i386 _and_ x86_64 configured.
-            # in this case, just pick the first entry as they are both noarch
-            try:
-                txmbr = self.yumbase.install(po=pkgs[0])
-            except Exception, e:
-                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
-            if txmbr:
-                try:
-                    self._runYumTransaction()
-                except PkError, e:
-                    self.error(e.code, e.details, exit=False)
-                    return
-            else:
-                self.error(ERROR_INTERNAL_ERROR, "could not install preupgrade as no transaction")
-        elif len(pkgs) == 1:
-
-            # check if there are any updates to the preupgrade package
-            po = pkgs[0]
-
-            # find preupgrade, which may not exist
-            try:
-                pkgs = self.yumbase.pkgSack.returnNewestByName(name='preupgrade')
-            except yum.Errors.PackageSackError, e:
-                # could not find upgraded preupgrade package in any enabled repos
-                pass
-            except Exception, e:
-                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
-            else:
-                if pkgs:
-                    newest = pkgs[0]
-                    if newest.EVR > po.EVR:
-                        # need to update preupgrade package
-                        try:
-                            txmbr = self.yumbase.update(po=pkgs[0])
-                        except Exception, e:
-                            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
-                        if txmbr:
-                            try:
-                                self._runYumTransaction()
-                            except PkError, e:
-                                self.error(e.code, e.details, exit=False)
-                                return
-        else:
-            self.error(ERROR_INTERNAL_ERROR, "more than one preupgrade package installed")
-
         # parse the releases file
         config = ConfigParser.ConfigParser()
         config.read('/usr/share/preupgrade/releases.list')
commit d5cd70493ce9ba7732ccffd7ea5f1789f8b33284
Author: Richard Hughes <richard at hughsie.com>
Date:   Wed Jul 15 10:37:13 2009 +0100

    Fix build with the latest PolicyKit

diff --git a/src/Makefile.am b/src/Makefile.am
index 9a48b12..78e7b00 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -32,6 +32,7 @@ INCLUDES =						\
 	-DEGG_VERBOSE="\"PK_VERBOSE\""			\
 	-DEGG_LOGGING="\"PK_LOGGING\""			\
 	-DEGG_CONSOLE="\"PK_CONSOLE\""			\
+	-DPOLKIT_LOCAL_I_KNOW_API_IS_SUBJECT_TO_CHANGE	\
 	-I$(top_srcdir)/lib				\
 	$(NULL)
 
commit a2610210a1dd0a207b8090cc5ceea93ac50c4d85
Author: Richard Hughes <richard at hughsie.com>
Date:   Wed Jul 15 10:22:02 2009 +0100

    Add some rationalle to the chosen PolicyKit policy decisions

diff --git a/policy/org.freedesktop.packagekit.policy.in b/policy/org.freedesktop.packagekit.policy.in
index c683de7..e7d000b 100644
--- a/policy/org.freedesktop.packagekit.policy.in
+++ b/policy/org.freedesktop.packagekit.policy.in
@@ -14,6 +14,11 @@
   <icon_name>package-x-generic</icon_name>
 
   <action id="org.freedesktop.packagekit.cancel-foreign">
+    <!-- SECURITY:
+          - Normal users are allowed to cancel their own task without
+            authentication, but a different user id needs the admin password
+            to cancel another users task.
+     -->
     <_description>Cancel foreign task</_description>
     <_message>Authentication is required to cancel a task that was not started by yourself</_message>
     <icon_name>package-x-generic</icon_name>
@@ -25,6 +30,12 @@
   </action>
 
   <action id="org.freedesktop.packagekit.package-install">
+    <!-- SECURITY:
+          - Normal users do not need authentication to install signed packages
+            from signed repositories, as this cannot exploit a system.
+          - Paranoid users (or parents!) can change this to 'auth_admin' or
+            'auth_admin_keep'.
+     -->
     <_description>Install signed package</_description>
     <_message>Authentication is required to install a signed package</_message>
     <icon_name>package-x-generic</icon_name>
@@ -36,6 +47,12 @@
   </action>
 
   <action id="org.freedesktop.packagekit.package-install-untrusted">
+    <!-- SECURITY:
+          - Normal users require admin authentication to install untrusted or
+            unrecognised packages, as allowing users to do this without a
+            password would be a massive security hole.
+          - This is not retained as each package should be authenticated.
+     -->
     <_description>Install untrusted local file</_description>
     <_message>Authentication is required to install an untrusted package</_message>
     <icon_name>package-x-generic</icon_name>
@@ -47,6 +64,12 @@
   </action>
 
   <action id="org.freedesktop.packagekit.system-trust-signing-key">
+    <!-- SECURITY:
+          - Normal users require admin authentication to add signing keys.
+          - This implies adding an explicit trust, and should not be granted
+            without a secure authentication.
+          - This is not kept as each package should be authenticated.
+     -->
     <_description>Trust a key used for signing packages</_description>
     <_message>Authentication is required to consider a key used for signing packages as trusted</_message>
     <icon_name>package-x-generic</icon_name>
@@ -58,6 +81,12 @@
   </action>
 
   <action id="org.freedesktop.packagekit.package-eula-accept">
+    <!-- SECURITY:
+          - Normal users do not require admin authentication to accept new
+            licence agreements.
+          - Change this to 'auth_admin' for environments where users should not
+            be given the option to make legal decisions.
+     -->
     <_description>Accept EULA</_description>
     <_message>Authentication is required to accept a EULA</_message>
     <icon_name>package-x-generic</icon_name>
@@ -69,6 +98,15 @@
   </action>
 
   <action id="org.freedesktop.packagekit.package-remove">
+    <!-- SECURITY:
+          - Normal users require admin authentication to remove packages as
+            this can make the system unbootable or stop other applications from
+            working.
+          - Be sure to close the tool used to remove the packages after the
+            admin authentication has been obtained, otherwise packages can still
+            be removed. If this is not possible, change this authentication to
+            'auth_admin'.
+     -->
     <_description>Remove package</_description>
     <_message>Authentication is required to remove packages</_message>
     <icon_name>package-x-generic</icon_name>
@@ -80,6 +118,13 @@
   </action>
 
   <action id="org.freedesktop.packagekit.system-update">
+    <!-- SECURITY:
+          - Normal users do not require admin authentication to update the
+            system as the packages will be signed, and the action is required
+            to update the system when unattended.
+          - Changing this to anything other than 'yes' will break unattended
+            updates.
+     -->
     <_description>Update packages</_description>
     <_message>Authentication is required to update packages</_message>
     <icon_name>package-x-generic</icon_name>
@@ -91,6 +136,11 @@
   </action>
 
   <action id="org.freedesktop.packagekit.system-rollback">
+    <!-- SECURITY:
+          - Normal users require admin authentication to rollback system state
+            as this will change a large number of packages, and could expose the
+            system to previously patched security vulnerabilities.
+     -->
     <_description>Rollback to a previous transaction</_description>
     <_message>Authentication is required to rollback a transaction</_message>
     <icon_name>package-x-generic</icon_name>
@@ -102,6 +152,11 @@
   </action>
 
   <action id="org.freedesktop.packagekit.system-sources-configure">
+    <!-- SECURITY:
+          - Normal users require admin authentication to enable or disable
+            software sources as this can be used to enable new updates or
+            install different versions of software.
+     -->
     <_description>Change software source parameters</_description>
     <_message>Authentication is required to change software source parameters</_message>
     <icon_name>package-x-generic</icon_name>
@@ -113,6 +168,10 @@
   </action>
 
   <action id="org.freedesktop.packagekit.system-sources-refresh">
+    <!-- SECURITY:
+          - Normal users do not require admin authentication to refresh the
+            cache, as this doesn't actually install or remove software.
+     -->
     <_description>Refresh system sources</_description>
     <_message>Authentication is required to refresh the system sources</_message>
     <icon_name>package-x-generic</icon_name>
@@ -124,6 +183,10 @@
   </action>
 
   <action id="org.freedesktop.packagekit.system-network-proxy-configure">
+    <!-- SECURITY:
+          - Normal users do not require admin authentication to set the proxy
+            used for downloading packages.
+     -->
     <_description>Set network proxy</_description>
     <_message>Authentication is required to set the network proxy used for downloading packages</_message>
     <icon_name>preferences-system-network-proxy</icon_name>
commit 3999deb4a72798f4feba10046e0197d0492b615a
Author: Richard Hughes <richard at hughsie.com>
Date:   Wed Jul 15 10:06:57 2009 +0100

    Rewrite the browser plugin in C, to fix several issues and add functionality

diff --git a/contrib/browser-plugin/Makefile.am b/contrib/browser-plugin/Makefile.am
index 535456c..6e538ef 100644
--- a/contrib/browser-plugin/Makefile.am
+++ b/contrib/browser-plugin/Makefile.am
@@ -5,21 +5,26 @@ packagekit_plugin_la_LDFLAGS = -rpath $(plugindir) -module -avoid-version -no-un
 packagekit_plugin_la_CPPFLAGS =			\
 	$(PK_BROWSER_PLUGIN_CFLAGS)		\
 	-DPACKAGE_LOCALE_DIR=\"$(localedir)\"	\
-	-DXP_UNIX				\
-	-I$(srcdir)/sdk				\
+	-DMOZ_X11				\
 	-I$(top_srcdir)/lib
 
+packagekit_plugin_la_CFLAGS =			\
+	$(WARNINGFLAGS_C)			\
+	$(NULL)
+
 packagekit_plugin_la_LIBADD =			\
 	$(PK_BROWSER_PLUGIN_LIBS)		\
 	$(top_builddir)/lib/packagekit-glib/libpackagekit-glib.la
 
 packagekit_plugin_la_SOURCES =			\
-	sdk/npplat.h				\
-	sdk/pluginbase.h			\
-	src/contents.cpp			\
-	src/contents.h				\
-	src/plugin.cpp				\
-	src/plugin.h
+	pk-store.c				\
+	pk-store.h				\
+	pk-plugin.c				\
+	pk-plugin.h				\
+	pk-plugin-install.c			\
+	pk-plugin-install.h			\
+	pk-main.c				\
+	pk-main.h
 
 EXTRA_DIST =					\
 	tests/test.html
diff --git a/contrib/browser-plugin/README b/contrib/browser-plugin/README
index 3a5b901..ccf5951 100644
--- a/contrib/browser-plugin/README
+++ b/contrib/browser-plugin/README
@@ -135,18 +135,6 @@ Using em sizes rather than points is probably a good idea to help with the
 font size issue, but it doesn't completely resolve it since the text of the
 plugin will be sized based on the system font size, not on the web pages font.
 
-License and Copyright
-=====================
-
-You may distribute the code under the terms of the Mozilla Public License 1.1,
-GNU General Public License Version 2 or later (the "GPL"), or the
-GNU Lesser General Public License Version 2.1 or later (the "LGPL"). See the
-source files for details and for copyright information.
-
-The plugin infrastructure (The sdk/ directory and much of src/plugin.cpp
-and src/plugin.h) is derived from the Mozilla plugin SDK; other code under
-src/ is new code written by Red Hat, Inc.
-
 Owen Taylor <otaylor at redhat.com>
 July 24, 2008
 
diff --git a/contrib/browser-plugin/pk-main.c b/contrib/browser-plugin/pk-main.c
new file mode 100644
index 0000000..e0ef751
--- /dev/null
+++ b/contrib/browser-plugin/pk-main.c
@@ -0,0 +1,471 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2008-2009 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#include <config.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n-lib.h>
+
+#include <npapi.h>
+#include <npfunctions.h>
+#include <npruntime.h>
+
+#define __USE_GNU
+#include <dlfcn.h>
+
+#include "pk-main.h"
+#include "pk-store.h"
+#include "pk-plugin.h"
+#include "pk-plugin-install.h"
+
+static NPNetscapeFuncs *npnfuncs = NULL;
+static void *module_handle = NULL;
+static PkStore *store = NULL;
+
+#ifndef HIBYTE
+#define HIBYTE(x) ((((uint32_t)(x)) & 0xff00) >> 8)
+#endif
+
+/**
+ * pk_debug_real:
+ **/
+void
+pk_debug_real (const gchar *func, const gchar *file, const int line, const gchar *format, ...)
+{
+	va_list args;
+	gchar *buffer = NULL;
+
+	if (g_getenv ("PK_DEBUG") == NULL)
+		return;
+
+	va_start (args, format);
+	g_vasprintf (&buffer, format, args);
+	va_end (args);
+
+	g_print ("FN:%s FC:%s LN:%i\n\t%s\n", file, func, line, buffer);
+
+	g_free(buffer);
+}
+
+/**
+ * pk_warning_real:
+ **/
+void
+pk_warning_real (const gchar *func, const gchar *file, const int line, const gchar *format, ...)
+{
+	va_list args;
+	gchar *buffer = NULL;
+
+	va_start (args, format);
+	g_vasprintf (&buffer, format, args);
+	va_end (args);
+
+	g_print ("FN:%s FC:%s LN:%i\n\t%s\n", file, func, line, buffer);
+
+	g_free(buffer);
+}
+
+/**
+ * pk_main_refresh_cb:
+ **/
+static void
+pk_main_refresh_cb (PkPlugin *plugin_, NPP instance)
+{
+	NPRect rect;
+	guint width;
+	guint height;
+
+	pk_debug ("pk_main_refresh_cb [%p]", instance);
+
+	/* invalid */
+	if (plugin_ == NULL) {
+		pk_warning ("NULL plugin");
+		return;
+	}
+
+	/* get parameters */
+	g_object_get (plugin_,
+		      "width", &width,
+		      "height", &height,
+		      NULL);
+
+	/* Coordinates here are relative to the plugin's origin (x,y) */
+	rect.left = 0;
+	rect.right =  width;
+	rect.top = 0;
+	rect.bottom = height;
+
+	pk_debug ("invalidating rect %ix%i to %ix%i", rect.left, rect.top, rect.right, rect.bottom);
+
+	npnfuncs->invalidaterect (instance, &rect);
+}
+
+/**
+ * pk_main_get_value:
+ **/
+static NPError
+pk_main_get_value (NPP instance, NPPVariable variable, void *value)
+{
+	NPError err = NPERR_NO_ERROR;
+	switch (variable) {
+	case NPPVpluginNameString:
+		* ((const gchar **)value) = "PackageKit";
+		break;
+	case NPPVpluginDescriptionString:
+		* ((const gchar **)value) = "Plugin for Installing Applications (new)";
+		break;
+	case NPPVpluginScriptableIID:
+	case NPPVpluginScriptableInstance:
+                /* XPCOM scripting, obsolete */
+                err = NPERR_GENERIC_ERROR;
+		break;
+	case NPPVpluginScriptableNPObject:
+		err = NPERR_INVALID_PLUGIN_ERROR;
+		break;
+	case NPPVpluginNeedsXEmbed:
+		* ((PRBool *)value) = PR_TRUE;
+		break;
+	default:
+		pk_warning ("Unhandled variable %d instance %p", variable, instance);
+		err = NPERR_INVALID_PARAM;
+	}
+	return err;
+}
+
+/**
+ * pk_main_newp:
+ **/
+static NPError
+pk_main_newp (NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char *argn[], char *argv[], NPSavedData *saved)
+{
+	gint i;
+	PkPlugin *plugin;
+
+	pk_debug ("new [%p]", instance);
+
+	/* create new content instance */
+	plugin = PK_PLUGIN (pk_plugin_install_new ());
+	g_signal_connect (plugin, "refresh", G_CALLBACK (pk_main_refresh_cb), instance);
+
+	/* set data */
+	for (i=0; i<argc; i++) {
+		if (g_strcmp0 (argn[i], "displayname") == 0 ||
+		    g_strcmp0 (argn[i], "packagenames") == 0)
+			pk_plugin_set_data (plugin, argn[i], argv[i]);
+	}
+
+	/* add to list */
+	pk_store_add_plugin (store, instance, plugin);
+
+	npnfuncs->setvalue (instance, NPPVpluginWindowBool, (void *) FALSE);
+
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * pk_main_destroy:
+ **/
+static NPError
+pk_main_destroy (NPP instance, NPSavedData **save)
+{
+	gboolean ret;
+	PkPlugin *plugin;
+
+	pk_debug ("pk_main_destroy [%p]", instance);
+
+	/* remove from list */
+	ret = pk_store_remove_plugin (store, instance);
+	if (!ret)
+		return NPERR_GENERIC_ERROR;
+
+	/* find plugin */
+	plugin = pk_store_lookup_plugin (store, instance);
+	if (plugin == NULL)
+		return NPERR_GENERIC_ERROR;
+
+	/* free content instance */
+	g_signal_handlers_disconnect_by_func (plugin, G_CALLBACK (pk_main_refresh_cb), instance);
+	g_object_unref (plugin);
+
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * pk_main_handle_event:
+ **/
+static NPError
+pk_main_handle_event (NPP instance, void *event)
+{
+	XEvent *xev = (XEvent *)event;
+	cairo_surface_t *surface;
+	cairo_t *cr;
+	XButtonEvent *xbe;
+	XGraphicsExposeEvent *xge;
+	XMotionEvent *xme;
+	XCrossingEvent *xce;
+	guint x;
+	guint y;
+	guint width;
+	guint height;
+	Display *display;
+	Visual *visual;
+	PkPlugin *plugin;
+
+	pk_debug ("pk_main_handle_event [%p]", instance);
+
+	/* find plugin */
+	plugin = pk_store_lookup_plugin (store, instance);
+	if (plugin == NULL)
+		return NPERR_GENERIC_ERROR;
+
+	switch (xev->xany.type) {
+	case GraphicsExpose:
+		xge = (XGraphicsExposeEvent *)event;
+
+		/* get parameters */
+		g_object_get (plugin,
+			      "x", &x,
+			      "y", &y,
+			      "width", &width,
+			      "height", &height,
+			      "display", &display,
+			      "visual", &visual,
+			      NULL);
+
+		pk_debug ("creating surface on display %i size %ix%i on drawable %i with visual %p",
+			 (int)display, x + width, y + height, (gint)xge->drawable, visual);
+
+		surface = cairo_xlib_surface_create (display, xge->drawable, visual, x + width, y + height);
+
+		width = cairo_xlib_surface_get_width (surface);
+		height = cairo_xlib_surface_get_height (surface);
+		if (width <= 0 || height <= 0)
+			pk_warning ("did not create surface: %ix%i", width, height);
+
+		cr = cairo_create (surface);
+		pk_plugin_draw (plugin, cr);
+		cairo_destroy (cr);
+		cairo_surface_destroy (surface);
+		return 1;
+	case ButtonPress:
+		xbe = (XButtonEvent *)event;
+		pk_plugin_button_press (plugin, xbe->x, xbe->y, xbe->time);
+		return 1;
+	case ButtonRelease:
+		xbe = (XButtonEvent *)event;
+		pk_plugin_button_release (plugin, xbe->x, xbe->y, xbe->time);
+		return 1;
+	case MotionNotify:
+		xme = (XMotionEvent *)event;
+		pk_plugin_motion (plugin, xme->x, xme->y);
+		return 1;
+	case EnterNotify:
+		xce = (XCrossingEvent *)event;
+		pk_plugin_enter (plugin, xce->x, xce->y);
+		return 1;
+	case LeaveNotify:
+		xce = (XCrossingEvent *)event;
+		pk_plugin_leave (plugin, xce->x, xce->y);
+		return 1;
+	}
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * pk_main_set_window:
+ **/
+static NPError
+pk_main_set_window (NPP instance, NPWindow* pNPWindow)
+{
+	gboolean ret;
+	gboolean started;
+	PkPlugin *plugin;
+	NPSetWindowCallbackStruct *ws_info;
+
+	pk_debug ("pk_main_set_window [%p]", instance);
+
+	/* shutdown */
+	if (pNPWindow == NULL) {
+		pk_warning ("NULL window");
+		return NPERR_GENERIC_ERROR;
+	}
+
+	/* find plugin */
+	plugin = pk_store_lookup_plugin (store, instance);
+	if (plugin == NULL) {
+		pk_warning ("NULL plugin");
+		return NPERR_GENERIC_ERROR;
+	}
+
+	ws_info = (NPSetWindowCallbackStruct *) pNPWindow->ws_info;
+
+	/* no visual yet */
+	if (ws_info->visual == NULL) {
+		pk_debug ("no visual, so skipping");
+		goto out;
+	}
+
+	/* set parameters */
+	g_object_set (plugin,
+		      "x", pNPWindow->x,
+		      "y", pNPWindow->y,
+		      "width", pNPWindow->width,
+		      "height", pNPWindow->height,
+		      "display", ws_info->display,
+		      "visual", ws_info->visual,
+		      NULL);
+
+	pk_debug ("x=%i, y=%i, width=%i, height=%i, display=%p, visual=%p",
+		 pNPWindow->x, pNPWindow->y, pNPWindow->width, pNPWindow->height,
+		 ws_info->display, ws_info->visual);
+
+	/* is already started */
+	g_object_get (plugin,
+		      "started", &started,
+		      NULL);
+	if (started) {
+		pk_debug ("already started, so skipping");
+		goto out;
+	}
+
+	/* start plugin */
+	ret = pk_plugin_start (plugin);
+	if (!ret)
+		pk_warning ("failed to start plugin");
+out:
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * pk_main_make_module_resident:
+ *
+ * If our dependent libraries like libpackagekit get unloaded, bad stuff
+ * happens (they may have registered GLib types and so forth) so we need
+ * to keep them around. The (GNU extension) RTLD_NODELETE seems useful
+ * but isn't so much, since it only refers to a specific library and not
+ * its dependent libraries, so we'd have to identify specifically each
+ * of our dependencies that is not safe to unload and that is most of
+ * the GTK+ stack.
+ **/
+static void
+pk_main_make_module_resident (void)
+{
+	Dl_info info;
+
+	/* get the absolute filename of this module */
+	if (!dladdr((void *)NP_GetMIMEDescription, &info)) {
+		g_warning("Can't find filename for module");
+		return;
+	}
+
+	/* now reopen it to get our own handle */
+	module_handle = dlopen(info.dli_fname, RTLD_NOW);
+	if (!module_handle) {
+		g_warning("Can't permanently open module %s", dlerror());
+		return;
+	}
+}
+
+NPError NP_GetEntryPoints (NPPluginFuncs *nppfuncs);
+
+/**
+ * NP_GetEntryPoints:
+ **/
+NPError
+NP_GetEntryPoints (NPPluginFuncs *nppfuncs)
+{
+	pk_debug ("NP_GetEntryPoints");
+
+	nppfuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
+	nppfuncs->newp = pk_main_newp;
+	nppfuncs->destroy = pk_main_destroy;
+	nppfuncs->getvalue = pk_main_get_value;
+	nppfuncs->event = pk_main_handle_event;
+	nppfuncs->setwindow = pk_main_set_window;
+
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * NP_Initialize:
+ **/
+NPError
+NP_Initialize (NPNetscapeFuncs *npnf, NPPluginFuncs *nppfuncs)
+{
+	pk_debug ("NP_Initialize");
+
+	if (npnf == NULL)
+		return NPERR_INVALID_FUNCTABLE_ERROR;
+
+	if (HIBYTE (npnf->version) > NP_VERSION_MAJOR)
+		return NPERR_INCOMPATIBLE_VERSION_ERROR;
+
+	/* already initialized */
+	if (module_handle != NULL)
+		return NPERR_NO_ERROR;
+
+	store = pk_store_new ();
+
+	/* if libpackagekit get unloaded, bad stuff happens */
+	pk_main_make_module_resident ();
+
+#ifdef ENABLE_NLS
+	bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
+
+	npnfuncs = npnf;
+	NP_GetEntryPoints (nppfuncs);
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * NP_Shutdown:
+ **/
+NPError
+NP_Shutdown ()
+{
+	pk_debug ("NP_Shutdown");
+
+	g_object_unref (store);
+	return NPERR_NO_ERROR;
+}
+
+/**
+ * NP_GetMIMEDescription:
+ **/
+char *
+NP_GetMIMEDescription (void)
+{
+	pk_debug ("NP_GetMIMEDescription");
+
+	return (gchar*) "application/x-packagekit-plugin:bsc:PackageKit Plugin";
+}
+
+/**
+ * NP_GetValue:
+ **/
+NPError
+NP_GetValue (void *npp, NPPVariable variable, void *value)
+{
+	return pk_main_get_value ((NPP)npp, variable, value);
+}
+
diff --git a/contrib/browser-plugin/pk-main.h b/contrib/browser-plugin/pk-main.h
new file mode 100644
index 0000000..9529811
--- /dev/null
+++ b/contrib/browser-plugin/pk-main.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#ifndef __PK_MAIN_H
+#define __PK_MAIN_H
+
+G_BEGIN_DECLS
+
+#define PK_PLUGIN_INSTALL_MARGIN	5 /* px */
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+#define pk_debug(...) pk_debug_real (__func__, __FILE__, __LINE__, __VA_ARGS__)
+#define pk_warning(...) pk_warning_real (__func__, __FILE__, __LINE__, __VA_ARGS__)
+#elif defined(__GNUC__) && __GNUC__ >= 3
+#define pk_debug(...) pk_debug_real (__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+#define pk_warning(...) pk_warning_real (__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+#else
+#define pk_debug(...)
+#define pk_warning(...)
+#endif
+
+void		pk_debug_real			(const gchar	*func,
+						 const gchar	*file,
+						 int		 line,
+						 const gchar	*format, ...) __attribute__((format (printf,4,5)));
+void		pk_warning_real			(const gchar	*func,
+						 const gchar	*file,
+						 int		 line,
+						 const gchar	*format, ...) __attribute__((format (printf,4,5)));
+
+G_END_DECLS
+
+#endif /* __PK_MAIN_H */
diff --git a/contrib/browser-plugin/pk-plugin-install.c b/contrib/browser-plugin/pk-plugin-install.c
new file mode 100644
index 0000000..7d18d4c
--- /dev/null
+++ b/contrib/browser-plugin/pk-plugin-install.c
@@ -0,0 +1,1004 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008-2009 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#include <config.h>
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gio/gdesktopappinfo.h>
+#include <pango/pangocairo.h>
+#include <dbus/dbus-glib.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "pk-main.h"
+#include "pk-plugin-install.h"
+
+#define PK_PLUGIN_INSTALL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PK_TYPE_PLUGIN_INSTALL, PkPluginInstallPrivate))
+
+typedef enum {
+	IN_PROGRESS, /* looking up package information */
+	INSTALLED,   /* package installed */
+	UPGRADABLE,  /* package installed, newer version available */
+	AVAILABLE,   /* package not installed, version available */
+	UNAVAILABLE, /* package not installed or available */
+	INSTALLING   /* currently installing a new version */
+} PkPluginInstallPackageStatus;
+
+struct PkPluginInstallPrivate
+{
+	PkPluginInstallPackageStatus	 status;
+	gchar			*available_version;
+	gchar			*available_package_name;
+	gchar			*installed_version;
+	gchar			*installed_package_name;
+	GAppInfo		*app_info;
+	gchar			*display_name;
+	gchar			**package_names;
+	PangoLayout		*pango_layout;
+	GPtrArray		*clients;
+	DBusGProxy		*install_package_proxy;
+	DBusGProxyCall		*install_package_call;
+};
+
+G_DEFINE_TYPE (PkPluginInstall, pk_plugin_install, PK_TYPE_PLUGIN)
+
+/**
+ * pk_plugin_install_clear_layout:
+ **/
+static void
+pk_plugin_install_clear_layout (PkPluginInstall *self)
+{
+	pk_debug ("clearing layout");
+
+	if (self->priv->pango_layout) {
+		g_object_unref (self->priv->pango_layout);
+		self->priv->pango_layout = NULL;
+	}
+}
+
+/**
+ * pk_plugin_install_refresh:
+ **/
+static void
+pk_plugin_install_refresh (PkPluginInstall *self)
+{
+	pk_plugin_request_refresh (PK_PLUGIN (self));
+}
+
+/**
+ * pk_plugin_install_set_status:
+ **/
+static void
+pk_plugin_install_set_status (PkPluginInstall *self, PkPluginInstallPackageStatus status)
+{
+	if (self->priv->status != status) {
+		pk_debug ("setting status %u", status);
+		self->priv->status = status;
+	}
+}
+
+/**
+ * pk_plugin_install_set_available_version:
+ **/
+static void
+pk_plugin_install_set_available_version (PkPluginInstall *self, const gchar *version)
+{
+	pk_debug ("setting available version: %s", version);
+
+	g_free (self->priv->available_version);
+	self->priv->available_version = g_strdup (version);
+}
+
+/**
+ * pk_plugin_install_set_available_package_name:
+ **/
+static void
+pk_plugin_install_set_available_package_name (PkPluginInstall *self, const gchar *name)
+{
+	pk_debug ("setting available package name: %s", name);
+
+	g_free (self->priv->available_package_name);
+	self->priv->available_package_name = g_strdup (name);
+}
+
+/**
+ * pk_plugin_install_set_installed_package_name:
+ **/
+static void
+pk_plugin_install_set_installed_package_name (PkPluginInstall *self, const gchar *name)
+{
+	pk_debug ("setting installed package name: %s", name);
+
+	g_free (self->priv->installed_package_name);
+	self->priv->installed_package_name = g_strdup (name);
+}
+
+/**
+ * pk_plugin_install_set_installed_version:
+ **/
+static void
+pk_plugin_install_set_installed_version (PkPluginInstall *self, const gchar *version)
+{
+	pk_debug ("setting installed version: %s", version);
+
+	g_free (self->priv->installed_version);
+	self->priv->installed_version = g_strdup (version);
+}
+
+/**
+ * pk_plugin_install_get_best_desktop_file:
+ **/
+static gchar *
+pk_plugin_install_get_best_desktop_file (PkPluginInstall *self)
+{
+	GPtrArray *array = NULL;
+	PkDesktop *desktop;
+	GError *error = NULL;
+	gboolean ret;
+	gchar *data = NULL;
+	const gchar *package;
+
+	/* open desktop database */
+	desktop = pk_desktop_new ();
+	ret = pk_desktop_open_database (desktop, &error);
+	if (!ret) {
+		pk_warning ("failed to open database: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+
+	/* get files */
+	package = self->priv->installed_package_name;
+	if (package == NULL) {
+		pk_warning ("installed_package_name NULL so cannot get desktop file");
+		goto out;
+	}
+	array = pk_desktop_get_shown_for_package (desktop, package, &error);
+	if (array == NULL) {
+		pk_debug ("no data: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	if (array->len == 0) {
+		pk_debug ("no matches for %s", package);
+		goto out;
+	}
+
+	/* just use the first entry */
+	data = g_strdup (g_ptr_array_index (array, 0));
+out:
+	if (array != NULL) {
+		g_ptr_array_foreach (array, (GFunc) g_free, NULL);
+		g_ptr_array_free (array, TRUE);
+	}
+	g_object_unref (desktop);
+	return data;
+}
+
+/**
+ * pk_plugin_install_package_cb:
+ **/
+static void
+pk_plugin_install_package_cb (PkClient *client, const PkPackageObj *obj, PkPluginInstall *self)
+{
+	gchar *filename;
+
+	/* if we didn't use displayname, use the summary */
+	if (self->priv->display_name == NULL)
+		self->priv->display_name = g_strdup (obj->summary);
+
+	/* parse the data */
+	if (obj->info == PK_INFO_ENUM_AVAILABLE) {
+		if (self->priv->status == IN_PROGRESS)
+			pk_plugin_install_set_status (self, AVAILABLE);
+		else if (self->priv->status == INSTALLED)
+			pk_plugin_install_set_status (self, UPGRADABLE);
+		pk_plugin_install_set_available_version (self, obj->id->version);
+		pk_plugin_install_set_available_package_name (self, obj->id->name);
+
+#if 0
+		/* if we have data from the repo, override the user:
+		 *  * we don't want the remote site pretending to install another package
+		 *  * it might be localised if the backend supports it */
+		if (obj->summary != NULL && obj->summary[0] != '\0')
+			self->priv->display_name = g_strdup (obj->summary);
+#endif
+
+		pk_plugin_install_clear_layout (self);
+		pk_plugin_install_refresh (self);
+
+	} else if (obj->info == PK_INFO_ENUM_INSTALLED) {
+		if (self->priv->status == IN_PROGRESS)
+			pk_plugin_install_set_status (self, INSTALLED);
+		else if (self->priv->status == AVAILABLE)
+			pk_plugin_install_set_status (self, UPGRADABLE);
+		pk_plugin_install_set_installed_version (self, obj->id->version);
+		pk_plugin_install_set_installed_package_name (self, obj->id->name);
+
+		/* get desktop file information */
+		filename = pk_plugin_install_get_best_desktop_file (self);
+		if (filename != NULL) {
+			self->priv->app_info = G_APP_INFO (g_desktop_app_info_new_from_filename (filename));
+#if 0
+			/* override, as this will have translation */
+			self->priv->display_name = g_strdup (g_app_info_get_name (self->priv->app_info));
+#endif
+		}
+		g_free (filename);
+
+		if (self->priv->app_info != 0)
+			pk_plugin_install_set_status (self, INSTALLED);
+
+		pk_plugin_install_clear_layout (self);
+		pk_plugin_install_refresh (self);
+	}
+}
+
+static void pk_plugin_install_remove_client (PkPluginInstall *self, PkClient *client);
+
+/**
+ * pk_plugin_install_error_code_cb:
+ **/
+static void
+pk_plugin_install_error_code_cb (PkClient *client, PkErrorCodeEnum code, const gchar *details, PkPluginInstall *self)
+{
+	pk_warning ("Error getting data from PackageKit: %s\n", details);
+	pk_plugin_install_remove_client (self, client);
+
+	if (self->priv->clients->len == 0) {
+		if (self->priv->status == IN_PROGRESS) {
+			pk_plugin_install_set_status (self, UNAVAILABLE);
+			pk_plugin_install_clear_layout (self);
+			pk_plugin_install_refresh (self);
+		}
+	}
+}
+
+/**
+ * pk_plugin_install_finished_cb:
+ **/
+static void
+pk_plugin_install_finished_cb (PkClient *client, PkExitEnum exit, guint runtime, PkPluginInstall *self)
+{
+	pk_plugin_install_remove_client (self, client);
+
+	if (self->priv->clients->len == 0) {
+		if (self->priv->status == IN_PROGRESS) {
+			pk_plugin_install_set_status (self, UNAVAILABLE);
+			pk_plugin_install_clear_layout (self);
+			pk_plugin_install_refresh (self);
+		}
+	}
+}
+
+/**
+ * pk_plugin_install_remove_client:
+ **/
+static void
+pk_plugin_install_remove_client (PkPluginInstall *self, PkClient *client)
+{
+	guint i;
+	PkClient *client_tmp;
+	for (i=0; i<self->priv->clients->len; i++) {
+		client_tmp = g_ptr_array_index (self->priv->clients, i);
+		if (client_tmp == client) {
+			g_ptr_array_remove_index (self->priv->clients, i);
+			g_signal_handlers_disconnect_by_func (client, (void *)pk_plugin_install_package_cb, self);
+			g_signal_handlers_disconnect_by_func (client, (void *)pk_plugin_install_error_code_cb, self);
+			g_signal_handlers_disconnect_by_func (client, (void *)pk_plugin_install_finished_cb, self);
+			g_object_unref (client);
+			break;
+		}
+	}
+}
+
+/**
+ * pk_plugin_install_recheck:
+ **/
+static void
+pk_plugin_install_recheck (PkPluginInstall *self)
+{
+	guint i;
+	const gchar *data;
+	gchar **package_ids;
+	self->priv->status = IN_PROGRESS;
+	pk_plugin_install_set_available_version (self, NULL);
+	pk_plugin_install_set_available_package_name (self, NULL);
+	pk_plugin_install_set_installed_version (self, NULL);
+	pk_plugin_install_set_installed_package_name (self, NULL);
+
+	/* get data, if if does not exist */
+	if (self->priv->package_names == NULL) {
+		data = pk_plugin_get_data (PK_PLUGIN (self), "displayname");
+		self->priv->display_name = g_strdup (data);
+		data = pk_plugin_get_data (PK_PLUGIN (self), "packagenames");
+		self->priv->package_names = g_strsplit (data, " ", -1);
+	}
+
+	for (i=0; self->priv->package_names[i] != NULL; i++) {
+		GError *error = NULL;
+		PkClient *client = pk_client_new ();
+		g_signal_connect (client, "package", G_CALLBACK (pk_plugin_install_package_cb), self);
+		g_signal_connect (client, "error-code", G_CALLBACK (pk_plugin_install_error_code_cb), self);
+		g_signal_connect (client, "finished", G_CALLBACK (pk_plugin_install_finished_cb), self);
+		package_ids = pk_package_ids_from_id (self->priv->package_names[i]);
+		if (!pk_client_resolve (client, PK_FILTER_ENUM_NONE, package_ids, &error)) {
+			pk_warning ("%s", error->message);
+			g_clear_error (&error);
+			g_object_unref (client);
+		} else {
+			g_ptr_array_add (self->priv->clients, client);
+		}
+		g_strfreev (package_ids);
+	}
+
+	if (self->priv->clients->len == 0 && self->priv->status == IN_PROGRESS) {
+		pk_plugin_install_set_status (self, UNAVAILABLE);
+		pk_plugin_install_clear_layout (self);
+		pk_plugin_install_refresh (self);
+	}
+}
+
+/**
+ * pk_plugin_install_append_markup:
+ **/
+static void
+pk_plugin_install_append_markup (GString *str, const gchar *format, ...)
+{
+	va_list vap;
+	gchar *tmp;
+
+	va_start (vap, format);
+	tmp = g_markup_vprintf_escaped (format, vap);
+	va_end (vap);
+
+	g_string_append (str, tmp);
+	g_free (tmp);
+}
+
+/**
+ * pk_plugin_install_rgba_from_gdk_color:
+ **/
+static guint32
+pk_plugin_install_rgba_from_gdk_color (GdkColor *color)
+{
+	return (((color->red >> 8) << 24) |
+		((color->green >> 8) << 16) |
+		 ((color->blue >> 8) << 8) |
+		  0xff);
+}
+
+/**
+ * pk_plugin_install_set_source_from_rgba:
+ **/
+static void
+pk_plugin_install_set_source_from_rgba (cairo_t *cr, guint32 rgba)
+{
+	cairo_set_source_rgba (cr,
+			      ((rgba & 0xff000000) >> 24) / 255.,
+			      ((rgba & 0x00ff0000) >> 16) / 255.,
+			      ((rgba & 0x0000ff00) >> 8) / 255.,
+			      (rgba & 0x000000ff) / 255.);
+}
+
+/**
+ * pk_plugin_install_get_style:
+ *
+ * Retrieve the system colors and fonts.
+ * This looks incredibly expensive .... to create a GtkWindow for
+ * every expose ... but actually it's only moderately expensive;
+ * Creating a GtkWindow is just normal GObject creation overhead --
+ * the extra expense beyond that will come when we actually create
+ * the window.
+ **/
+static void
+pk_plugin_install_get_style (PangoFontDescription **font_desc, guint32 *foreground, guint32 *background, guint32 *link)
+{
+	GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	GtkStyle *style;
+	GdkColor link_color = { 0, 0, 0, 0xeeee };
+	GdkColor *tmp = NULL;
+
+	gtk_widget_ensure_style (window);
+
+	style = gtk_widget_get_style (window);
+	*foreground = pk_plugin_install_rgba_from_gdk_color (&style->text[GTK_STATE_NORMAL]);
+	*background = pk_plugin_install_rgba_from_gdk_color (&style->base[GTK_STATE_NORMAL]);
+
+	gtk_widget_style_get (GTK_WIDGET (window), "link-color", &tmp, NULL);
+	if (tmp != NULL) {
+		link_color = *tmp;
+		gdk_color_free (tmp);
+	}
+
+	*link = pk_plugin_install_rgba_from_gdk_color (&link_color);
+
+	*font_desc = pango_font_description_copy (style->font_desc);
+
+	gtk_widget_destroy (window);
+}
+
+/**
+ * pk_plugin_install_ensure_layout:
+ **/
+static void
+pk_plugin_install_ensure_layout (PkPluginInstall *self, cairo_t *cr, PangoFontDescription *font_desc, guint32 link_color)
+{
+	GString *markup = g_string_new (NULL);
+
+	if (self->priv->pango_layout != NULL)
+		return;
+
+	self->priv->pango_layout = pango_cairo_create_layout (cr);
+	pango_layout_set_font_description (self->priv->pango_layout, font_desc);
+
+	/* WARNING: Any changes to what links are created here will require corresponding
+	 * changes to the pk_plugin_install_button_release () method
+	 */
+	switch (self->priv->status) {
+	case IN_PROGRESS:
+		/* TRANSLATORS: when we are getting data from the daemon */
+		pk_plugin_install_append_markup (markup, _("Getting package information..."));
+		break;
+	case INSTALLED:
+		if (self->priv->app_info != 0) {
+			pk_plugin_install_append_markup (markup, "<span color='#%06x' underline='single'>", link_color >> 8);
+			/* TRANSLATORS: run an applicaiton */
+			pk_plugin_install_append_markup (markup, _("Run %s"), self->priv->display_name);
+			pk_plugin_install_append_markup (markup, "</span>");
+		} else
+			pk_plugin_install_append_markup (markup, "<big>%s</big>", self->priv->display_name);
+		if (self->priv->installed_version != NULL)
+			/* TRANSLATORS: show the installed version of a package */
+			pk_plugin_install_append_markup (markup, "\n<small>%s: %s</small>", _("Installed version"), self->priv->installed_version);
+		break;
+	case UPGRADABLE:
+		pk_plugin_install_append_markup (markup, "<big>%s</big>", self->priv->display_name);
+		if (self->priv->app_info != 0) {
+			if (self->priv->installed_version != NULL) {
+				pk_plugin_install_append_markup (markup, "\n<span color='#%06x' underline='single'>", link_color >> 8);
+				/* TRANSLATORS: run the application now */
+				pk_plugin_install_append_markup (markup, _("Run version %s now"), self->priv->installed_version);
+				pk_plugin_install_append_markup (markup, "</span>");
+			} else {
+				pk_plugin_install_append_markup (markup,
+				              "\n<span color='#%06x' underline='single'>%s</span>",
+					      /* TRANSLATORS: run the application now */
+					      _("Run now"), link_color >> 8);
+		        }
+		}
+
+		pk_plugin_install_append_markup (markup, "\n<span color='#%06x' underline='single'>", link_color >> 8);
+		/* TRANSLATORS: update to a new version of the package */
+		pk_plugin_install_append_markup (markup, _("Update to version %s"), self->priv->available_version);
+		pk_plugin_install_append_markup (markup, "</span>");
+		break;
+	case AVAILABLE:
+		pk_plugin_install_append_markup (markup, "<span color='#%06x' underline='single'>", link_color >> 8);
+		/* TRANSLATORS: To install a package */
+		pk_plugin_install_append_markup (markup, _("Install %s now"), self->priv->display_name);
+		pk_plugin_install_append_markup (markup, "</span>");
+		/* TRANSLATORS: the version of the package */
+		pk_plugin_install_append_markup (markup, "\n<small>%s: %s</small>", _("Version"), self->priv->available_version);
+		break;
+	case UNAVAILABLE:
+		pk_plugin_install_append_markup (markup, "<big>%s</big>", self->priv->display_name);
+		/* TRANSLATORS: noting found, so can't install */
+		pk_plugin_install_append_markup (markup, "\n<small>%s</small>", _("No packages found for your system"));
+		break;
+	case INSTALLING:
+		pk_plugin_install_append_markup (markup, "<big>%s</big>", self->priv->display_name);
+		/* TRANSLATORS: package is being installed */
+		pk_plugin_install_append_markup (markup, "\n<small>%s</small>", _("Installing..."));
+		break;
+	}
+
+	pango_layout_set_markup (self->priv->pango_layout, markup->str, -1);
+	g_string_free (markup, TRUE);
+}
+
+/**
+ * pk_plugin_install_get_package_icon:
+ **/
+static gchar *
+pk_plugin_install_get_package_icon (PkPluginInstall *self)
+{
+	gboolean ret;
+	GKeyFile *file;
+	gchar *data = NULL;
+	const gchar *filename;
+
+	/* do we have data? */
+	if (self->priv->installed_package_name == NULL) {
+		pk_debug ("installed_package_name NULL, so cannot get icon");
+		goto out;
+	}
+
+	/* get data from the best file */
+	file = g_key_file_new ();
+	filename = pk_plugin_install_get_best_desktop_file (self);
+	if (filename == NULL) {
+		pk_debug ("no desktop file");
+		goto out;
+	}
+	ret = g_key_file_load_from_file (file, filename, G_KEY_FILE_NONE, NULL);
+	if (!ret) {
+		pk_warning ("failed to open %s", filename);
+		goto out;
+	}
+	data = g_key_file_get_string (file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
+	g_key_file_free (file);
+out:
+	return data;
+}
+
+/**
+ * pk_plugin_install_start:
+ **/
+static gboolean
+pk_plugin_install_start (PkPlugin *plugin)
+{
+	PkPluginInstall *self = PK_PLUGIN_INSTALL (plugin);
+	pk_plugin_install_recheck (self);
+	return TRUE;
+}
+
+/**
+ * pk_plugin_install_draw:
+ **/
+static gboolean
+pk_plugin_install_draw (PkPlugin *plugin, cairo_t *cr)
+{
+	guint32 foreground, background, link;
+	PangoFontDescription *font_desc;
+	guint x;
+	guint y;
+	guint width;
+	guint height;
+	const gchar *filename;
+	GtkIconTheme *theme;
+	GdkPixbuf *pixbuf;
+	PkPluginInstall *self = PK_PLUGIN_INSTALL (plugin);
+
+	/* get parameters */
+	g_object_get (self,
+		      "x", &x,
+		      "y", &y,
+		      "width", &width,
+		      "height", &height,
+		      NULL);
+
+	pk_debug ("drawing on %ux%u (%ux%u)", x, y, width, height);
+
+	/* get properties */
+	pk_plugin_install_get_style (&font_desc, &foreground, &background, &link);
+
+       /* fill background */
+	pk_plugin_install_set_source_from_rgba (cr, background);
+	cairo_rectangle (cr, x, y, width, height);
+	cairo_fill (cr);
+
+        /* grey outline */
+	cairo_set_source_rgb (cr, 0.5, 0.5, 0.5);
+	cairo_rectangle (cr, x + 0.5, y + 0.5, width - 1, height - 1);
+	cairo_set_line_width (cr, 1);
+	cairo_stroke (cr);
+
+	/* get themed icon */
+	filename = pk_plugin_install_get_package_icon (self);
+	if (filename == NULL)
+		filename = "package-x-generic";
+	theme = gtk_icon_theme_get_default ();
+	pixbuf = gtk_icon_theme_load_icon (theme, filename, 48, GTK_ICON_LOOKUP_FORCE_SIZE, NULL);
+	if (pixbuf == NULL)
+		goto skip;
+	gdk_cairo_set_source_pixbuf (cr, pixbuf, x + PK_PLUGIN_INSTALL_MARGIN, y + PK_PLUGIN_INSTALL_MARGIN);
+	cairo_rectangle (cr, x + PK_PLUGIN_INSTALL_MARGIN, y + PK_PLUGIN_INSTALL_MARGIN, 48, 48);
+	cairo_fill (cr);
+	g_object_unref (pixbuf);
+
+skip:
+	/* write text */
+	pk_plugin_install_ensure_layout (self, cr, font_desc, link);
+	cairo_move_to (cr, (x + PK_PLUGIN_INSTALL_MARGIN*2) + 48, y + PK_PLUGIN_INSTALL_MARGIN + PK_PLUGIN_INSTALL_MARGIN);
+	pk_plugin_install_set_source_from_rgba (cr, foreground);
+	pango_cairo_show_layout (cr, self->priv->pango_layout);
+	return TRUE;
+}
+
+/**
+ * pk_plugin_install_line_is_terminated:
+ *
+ * Cut and paste from pango-layout.c; determines if a layout iter is on
+ * a line terminated by a real line break (rather than a line break from
+ * wrapping). We use this to determine whether the empty run at the end
+ * of a display line should be counted as a break between links or not.
+ *
+ * (Code in pango-layout.c is by me, Copyright Red Hat, and hereby relicensed
+ * to the license of this file)
+ **/
+static gboolean
+pk_plugin_install_line_is_terminated (PangoLayoutIter *iter)
+{
+	/* There is a real terminator at the end of each paragraph other
+	 * than the last.
+	 */
+	PangoLayoutLine *line = pango_layout_iter_get_line (iter);
+	GSList *lines = pango_layout_get_lines (pango_layout_iter_get_layout (iter));
+	GSList *link = g_slist_find (lines, line);
+	if (!link) {
+		pk_warning ("Can't find line in layout line list");
+		return FALSE;
+	}
+
+	if (link->next) {
+		PangoLayoutLine *next_line = (PangoLayoutLine *)link->next->data;
+		if (next_line->is_paragraph_start)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**
+ * pk_plugin_install_get_link_index:
+ *
+ * This function takes an X,Y position and determines whether it is over one
+ * of the underlined portions of the layout (a link). It works by iterating
+ * through the runs of the layout (a run is a segment with a consistent
+ * font and display attributes, more or less), and counting the underlined
+ * segments that we see. A segment that is underlined could be broken up
+ * into multiple runs if it is drawn with multiple fonts due to fonts
+ * substitution, so we actually count non-underlined => underlined
+ * transitions.
+ **/
+static gint
+pk_plugin_install_get_link_index (PkPluginInstall *self, gint x, gint y)
+{
+	gint idx;
+	gint trailing;
+	PangoLayoutIter *iter;
+	gint seen_links = 0;
+	gboolean in_link = FALSE;
+	gint result = -1;
+
+	/* Coordinates are relative to origin of plugin (different from drawing) */
+
+	if (!self->priv->pango_layout)
+		return -1;
+
+	x -= (PK_PLUGIN_INSTALL_MARGIN * 2) + 48;
+	y -= (PK_PLUGIN_INSTALL_MARGIN * 2);
+
+	if (!pango_layout_xy_to_index (self->priv->pango_layout, x * PANGO_SCALE, y * PANGO_SCALE, &idx, &trailing))
+		return - 1;
+
+	iter = pango_layout_get_iter (self->priv->pango_layout);
+	while (TRUE) {
+		PangoLayoutRun *run = pango_layout_iter_get_run (iter);
+		if (run) {
+			PangoItem *item = run->item;
+			PangoUnderline uline = PANGO_UNDERLINE_NONE;
+			GSList *l;
+
+			for (l = item->analysis.extra_attrs; l; l = l->next) {
+				PangoAttribute *attr = (PangoAttribute *)l->data;
+				if (attr->klass->type == PANGO_ATTR_UNDERLINE) {
+					uline = (PangoUnderline) ( (PangoAttrInt *)attr)->value;
+				}
+			}
+
+			if (uline == PANGO_UNDERLINE_NONE)
+				in_link = FALSE;
+			else if (!in_link) {
+				in_link = TRUE;
+				seen_links++;
+			}
+
+			if (item->offset <= idx && idx < item->offset + item->length) {
+				if (in_link)
+					result = seen_links - 1;
+
+				break;
+			}
+		} else {
+			/* We have an empty run at the end of each line. A line break doesn't
+			 * terminate the link, but a real newline does.
+			 */
+			if (pk_plugin_install_line_is_terminated (iter))
+				in_link = FALSE;
+		}
+
+		if (!pango_layout_iter_next_run (iter))
+			break;
+	}
+
+	pango_layout_iter_free (iter);
+
+	return result;
+}
+
+/**
+ * pk_plugin_install_method_finished_cb:
+ **/
+static void
+pk_plugin_install_method_finished_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
+{
+	PkPluginInstall *self = (PkPluginInstall *)user_data;
+	GError *error = NULL;
+
+	if (!dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID)) {
+		pk_warning ("Error occurred during install: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	g_object_unref (self->priv->install_package_proxy);
+	self->priv->install_package_proxy = NULL;
+	self->priv->install_package_call = NULL;
+
+	pk_plugin_install_recheck (self);
+}
+
+/**
+ * pk_plugin_install_install_package:
+ **/
+static void
+pk_plugin_install_install_package (PkPluginInstall *self, Time event_time)
+{
+	GdkEvent *event;
+	GdkWindow *window;
+	guint xid = 0;
+	gchar **packages;
+	DBusGConnection *connection;
+
+	if (self->priv->available_package_name == NULL) {
+		pk_warning ("No available package to install");
+		return;
+	}
+
+	if (self->priv->install_package_call != 0) {
+		pk_warning ("Already installing package");
+		return;
+	}
+
+	/* TODO: needs to be on Modify interface */
+	connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+	self->priv->install_package_proxy = dbus_g_proxy_new_for_name (connection,
+							 "org.freedesktop.PackageKit",
+							 "/org/freedesktop/PackageKit",
+							 "org.freedesktop.PackageKit.Modify");
+
+	/* will be NULL when activated not using a keyboard or a mouse */
+	event = gtk_get_current_event ();
+	if (event != NULL && event->any.window != NULL) {
+		window = gdk_window_get_toplevel (event->any.window);
+		xid = GDK_DRAWABLE_XID (window);
+	}
+
+	packages = g_strsplit (self->priv->available_package_name, ";", -1);
+	self->priv->install_package_call =
+		dbus_g_proxy_begin_call_with_timeout (self->priv->install_package_proxy,
+						      "InstallPackageNames",
+						      pk_plugin_install_method_finished_cb,
+						      self,
+						      (GDestroyNotify) 0,
+						      24 * 60 * 1000 * 1000, /* one day */
+						      G_TYPE_UINT, xid, /* xid */
+						      G_TYPE_STRV, packages,
+						      G_TYPE_STRING, "hide-finished",
+						      G_TYPE_INVALID,
+						      G_TYPE_INVALID);
+	g_strfreev (packages);
+
+	pk_plugin_install_set_status (self, INSTALLING);
+	pk_plugin_install_clear_layout (self);
+	pk_plugin_install_refresh (self);
+}
+
+/**
+ * pk_plugin_install_get_server_timestamp:
+ **/
+static guint32
+pk_plugin_install_get_server_timestamp ()
+{
+	GtkWidget *invisible = gtk_invisible_new ();
+	GdkWindow *window;
+	guint32 server_time;
+
+	gtk_widget_realize (invisible);
+	window = gtk_widget_get_window (invisible);
+	server_time = gdk_x11_get_server_time (window);
+	gtk_widget_destroy (invisible);
+	return server_time;
+}
+
+/**
+ * pk_plugin_install_run_application:
+ **/
+static void
+pk_plugin_install_run_application (PkPluginInstall *self, Time event_time)
+{
+	GError *error = NULL;
+	GdkAppLaunchContext *context;
+
+	if (self->priv->app_info == 0) {
+		pk_warning ("Didn't find application to launch");
+		return;
+	}
+
+	if (event_time == 0)
+		event_time = pk_plugin_install_get_server_timestamp ();
+
+	context = gdk_app_launch_context_new ();
+	gdk_app_launch_context_set_timestamp (context, event_time);
+	if (!g_app_info_launch (self->priv->app_info, NULL, G_APP_LAUNCH_CONTEXT (context), &error)) {
+		pk_warning ("%s\n", error->message);
+		g_clear_error (&error);
+		return;
+	}
+
+	if (context != NULL)
+		g_object_unref (context);
+}
+
+/**
+ * pk_plugin_install_button_release:
+ **/
+static gboolean
+pk_plugin_install_button_release (PkPlugin *plugin, gint x, gint y, Time event_time)
+{
+	PkPluginInstall *self = PK_PLUGIN_INSTALL (plugin);
+	gint idx = pk_plugin_install_get_link_index (self, x, y);
+	if (idx < 0)
+		return FALSE;
+
+	switch (self->priv->status) {
+	case IN_PROGRESS:
+	case INSTALLING:
+	case UNAVAILABLE:
+		break;
+	case INSTALLED:
+		if (self->priv->app_info != NULL)
+			pk_plugin_install_run_application (self, event_time);
+		break;
+	case UPGRADABLE:
+		if (self->priv->app_info != NULL && idx == 0)
+			pk_plugin_install_run_application (self, event_time);
+		else
+			pk_plugin_install_install_package (self, event_time);
+		break;
+	case AVAILABLE:
+		if (self->priv->available_package_name != NULL)
+			pk_plugin_install_install_package (self, event_time);
+		break;
+	}
+	return TRUE;
+}
+
+/**
+ * pk_plugin_install_finalize:
+ **/
+static void
+pk_plugin_install_finalize (GObject *object)
+{
+	PkPluginInstall *self;
+	g_return_if_fail (PK_IS_PLUGIN_INSTALL (object));
+	self = PK_PLUGIN_INSTALL (object);
+
+	pk_plugin_install_clear_layout (self);
+
+	if (self->priv->app_info != NULL)
+		g_object_unref (self->priv->app_info);
+
+	if (self->priv->install_package_call != NULL) {
+		dbus_g_proxy_cancel_call (self->priv->install_package_proxy, self->priv->install_package_call);
+		g_object_unref (self->priv->install_package_proxy);
+	}
+
+	/* remove clients */
+	while (self->priv->clients->len > 0)
+		pk_plugin_install_remove_client (self, g_ptr_array_index (self->priv->clients, 0));
+	g_ptr_array_free (self->priv->clients, TRUE);
+
+	G_OBJECT_CLASS (pk_plugin_install_parent_class)->finalize (object);
+}
+
+/**
+ * pk_plugin_install_class_init:
+ **/
+static void
+pk_plugin_install_class_init (PkPluginInstallClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	PkPluginClass *plugin_class = PK_PLUGIN_CLASS (klass);
+
+	object_class->finalize = pk_plugin_install_finalize;
+	plugin_class->start = pk_plugin_install_start;
+	plugin_class->draw = pk_plugin_install_draw;
+	plugin_class->button_release = pk_plugin_install_button_release;
+
+	g_type_class_add_private (klass, sizeof (PkPluginInstallPrivate));
+}
+
+/**
+ * pk_plugin_install_init:
+ **/
+static void
+pk_plugin_install_init (PkPluginInstall *self)
+{
+	self->priv = PK_PLUGIN_INSTALL_GET_PRIVATE (self);
+
+	self->priv->status = IN_PROGRESS;
+	self->priv->available_version = NULL;
+	self->priv->available_package_name = NULL;
+	self->priv->installed_version = NULL;
+	self->priv->installed_package_name = NULL;
+	self->priv->app_info = NULL;
+	self->priv->display_name = NULL;
+	self->priv->package_names = NULL;
+	self->priv->pango_layout = NULL;
+	self->priv->clients = g_ptr_array_new ();
+	self->priv->install_package_proxy = NULL;
+	self->priv->install_package_call = NULL;
+}
+
+/**
+ * pk_plugin_install_new:
+ * Return value: A new plugin_install class instance.
+ **/
+PkPluginInstall *
+pk_plugin_install_new (void)
+{
+	PkPluginInstall *self;
+	self = g_object_new (PK_TYPE_PLUGIN_INSTALL, NULL);
+	return PK_PLUGIN_INSTALL (self);
+}
+
+/***************************************************************************
+ ***                          MAKE CHECK TESTS                           ***
+ ***************************************************************************/
+#ifdef EGG_TEST
+#include "egg-test.h"
+
+void
+egg_test_plugin_install (EggTest *test)
+{
+	PkPluginInstall *self;
+
+	if (!egg_test_start (test, "PkPluginInstall"))
+		return;
+
+	/************************************************************/
+	egg_test_title (test, "get an instance");
+	self = pk_plugin_install_new ();
+	if (self != NULL)
+		egg_test_success (test, NULL);
+	else
+		egg_test_failed (test, NULL);
+
+	g_object_unref (self);
+
+	egg_test_end (test);
+}
+#endif
+
diff --git a/contrib/browser-plugin/pk-plugin-install.h b/contrib/browser-plugin/pk-plugin-install.h
new file mode 100644
index 0000000..e565c7b
--- /dev/null
+++ b/contrib/browser-plugin/pk-plugin-install.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard at hughsie.com>
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#ifndef __PK_PLUGIN_INSTALL_H
+#define __PK_PLUGIN_INSTALL_H
+
+#include <glib-object.h>
+#include <cairo-xlib.h>
+#include <packagekit-glib/packagekit.h>
+
+#include "pk-plugin.h"
+
+G_BEGIN_DECLS
+
+#define PK_TYPE_PLUGIN_INSTALL		(pk_plugin_install_get_type ())
+#define PK_PLUGIN_INSTALL(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), PK_TYPE_PLUGIN_INSTALL, PkPluginInstall))
+#define PK_PLUGIN_INSTALL_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), PK_TYPE_PLUGIN_INSTALL, PkPluginInstallClass))
+#define PK_IS_PLUGIN_INSTALL(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), PK_TYPE_PLUGIN_INSTALL))
+#define PK_IS_PLUGIN_INSTALL_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), PK_TYPE_PLUGIN_INSTALL))
+#define PK_PLUGIN_INSTALL_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), PK_TYPE_PLUGIN_INSTALL, PkPluginInstallClass))
+
+typedef struct PkPluginInstallPrivate PkPluginInstallPrivate;
+
+typedef struct
+{
+	PkPlugin		 parent;
+	PkPluginInstallPrivate	*priv;
+} PkPluginInstall;
+
+typedef struct
+{
+	PkPluginClass		 parent_class;
+} PkPluginInstallClass;
+
+GType		 pk_plugin_install_get_type		(void);
+PkPluginInstall	*pk_plugin_install_new			(void);
+
+G_END_DECLS
+
+#endif /* __PK_PLUGIN_INSTALL_H */
diff --git a/contrib/browser-plugin/pk-plugin.c b/contrib/browser-plugin/pk-plugin.c
new file mode 100644
index 0000000..fc8522a
--- /dev/null
+++ b/contrib/browser-plugin/pk-plugin.c
@@ -0,0 +1,431 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#include <config.h>
+
+#include <glib-object.h>
+
+#include "pk-main.h"
+#include "pk-plugin.h"
+
+#define PK_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PK_TYPE_PLUGIN, PkPluginPrivate))
+
+struct PkPluginPrivate
+{
+	gboolean		 started;
+	guint			 x;
+	guint			 y;
+	guint			 width;
+	guint			 height;
+	Display			*display;
+	Visual			*visual;
+	GHashTable		*data;
+};
+
+enum {
+	SIGNAL_REFRESH,
+	SIGNAL_LAST
+};
+
+enum {
+	PROP_0,
+	PROP_X,
+	PROP_Y,
+	PROP_WIDTH,
+	PROP_HEIGHT,
+	PROP_DISPLAY,
+	PROP_VISUAL,
+	PROP_STARTED,
+	PROP_LAST,
+};
+
+static guint signals [SIGNAL_LAST] = { 0 };
+
+G_DEFINE_TYPE (PkPlugin, pk_plugin, G_TYPE_OBJECT)
+
+/**
+ * pk_plugin_set_data:
+ **/
+gboolean
+pk_plugin_set_data (PkPlugin *plugin, const gchar *name, const gchar *value)
+{
+	g_return_val_if_fail (PK_IS_PLUGIN (plugin), FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	g_hash_table_insert (plugin->priv->data, g_strdup (name), g_strdup (value));
+	pk_debug ("SET: name=%s, value=%s <%p>", name, value, plugin);
+
+	return TRUE;
+}
+
+/**
+ * pk_plugin_get_data:
+ **/
+const gchar *
+pk_plugin_get_data (PkPlugin *plugin, const gchar *name)
+{
+	const gchar *value;
+
+	g_return_val_if_fail (PK_IS_PLUGIN (plugin), FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+
+	value = g_hash_table_lookup (plugin->priv->data, name);
+	pk_debug ("GET: name=%s, value=%s <%p>", name, value, plugin);
+
+	return value;
+}
+
+/**
+ * pk_plugin_start:
+ **/
+gboolean
+pk_plugin_start (PkPlugin *plugin)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	pk_debug ("start <%p>", plugin);
+
+	/* already started, don't restart */
+	if (plugin->priv->started) {
+		pk_warning ("already started <%p>", plugin);
+		return FALSE;
+	}
+
+	/* no support */
+	if (klass->start == NULL)
+		return FALSE;
+	plugin->priv->started = klass->start (plugin);
+	return plugin->priv->started;
+}
+
+/**
+ * pk_plugin_draw:
+ **/
+gboolean
+pk_plugin_draw (PkPlugin *plugin, cairo_t *cr)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	/* no support */
+	if (klass->draw == NULL)
+		return FALSE;
+
+	pk_debug ("draw on %p <%p>", cr, plugin);
+
+	return klass->draw (plugin, cr);
+}
+
+/**
+ * pk_plugin_button_press:
+ **/
+gboolean
+pk_plugin_button_press (PkPlugin *plugin, gint x, gint y, Time event_time)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	/* no support */
+	if (klass->button_press == NULL)
+		return FALSE;
+
+	pk_debug ("button_press %i,%i <%p>", x, y, plugin);
+
+	return klass->button_press (plugin, x, y, event_time);
+}
+
+/**
+ * pk_plugin_button_release:
+ **/
+gboolean
+pk_plugin_button_release (PkPlugin *plugin, gint x, gint y, Time event_time)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	/* no support */
+	if (klass->button_release == NULL)
+		return FALSE;
+
+	pk_debug ("button_release %i,%i <%p>", x, y, plugin);
+
+	return klass->button_release (plugin, x, y, event_time);
+}
+
+/**
+ * pk_plugin_motion:
+ **/
+gboolean
+pk_plugin_motion (PkPlugin *plugin, gint x, gint y)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	/* no support */
+	if (klass->motion == NULL)
+		return FALSE;
+
+	pk_debug ("motion %i,%i <%p>", x, y, plugin);
+
+	return klass->motion (plugin, x, y);
+}
+
+/**
+ * pk_plugin_enter:
+ **/
+gboolean
+pk_plugin_enter (PkPlugin *plugin, gint x, gint y)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	/* no support */
+	if (klass->enter == NULL)
+		return FALSE;
+
+	pk_debug ("enter %i,%i <%p>", x, y, plugin);
+
+	return klass->enter (plugin, x, y);
+}
+
+/**
+ * pk_plugin_leave:
+ **/
+gboolean
+pk_plugin_leave (PkPlugin *plugin, gint x, gint y)
+{
+	PkPluginClass *klass = PK_PLUGIN_GET_CLASS (plugin);
+
+	/* no support */
+	if (klass->leave == NULL)
+		return FALSE;
+
+	pk_debug ("leave %i,%i <%p>", x, y, plugin);
+
+	return klass->leave (plugin, x, y);
+}
+
+/**
+ * pk_plugin_request_refresh:
+ **/
+gboolean
+pk_plugin_request_refresh (PkPlugin *plugin)
+{
+	g_return_val_if_fail (PK_IS_PLUGIN (plugin), FALSE);
+
+	pk_debug ("emit refresh <%p>", plugin);
+
+	g_signal_emit (plugin, signals [SIGNAL_REFRESH], 0);
+	return TRUE;
+}
+
+/**
+ * pk_plugin_get_property:
+ **/
+static void
+pk_plugin_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	PkPlugin *plugin = PK_PLUGIN (object);
+	switch (prop_id) {
+	case PROP_X:
+		g_value_set_uint (value, plugin->priv->x);
+		break;
+	case PROP_Y:
+		g_value_set_uint (value, plugin->priv->y);
+		break;
+	case PROP_WIDTH:
+		g_value_set_uint (value, plugin->priv->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_uint (value, plugin->priv->height);
+		break;
+	case PROP_DISPLAY:
+		g_value_set_pointer (value, plugin->priv->display);
+		break;
+	case PROP_VISUAL:
+		g_value_set_pointer (value, plugin->priv->visual);
+		break;
+	case PROP_STARTED:
+		g_value_set_boolean (value, plugin->priv->started);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * pk_plugin_set_property:
+ **/
+static void
+pk_plugin_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	PkPlugin *plugin = PK_PLUGIN (object);
+	switch (prop_id) {
+	case PROP_X:
+		plugin->priv->x = g_value_get_uint (value);
+		break;
+	case PROP_Y:
+		plugin->priv->y = g_value_get_uint (value);
+		break;
+	case PROP_WIDTH:
+		plugin->priv->width = g_value_get_uint (value);
+		break;
+	case PROP_HEIGHT:
+		plugin->priv->height = g_value_get_uint (value);
+		break;
+	case PROP_DISPLAY:
+		plugin->priv->display = g_value_get_pointer (value);
+		break;
+	case PROP_VISUAL:
+		plugin->priv->visual = g_value_get_pointer (value);
+		break;
+	case PROP_STARTED:
+		plugin->priv->started = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+/**
+ * pk_plugin_finalize:
+ **/
+static void
+pk_plugin_finalize (GObject *object)
+{
+	PkPlugin *plugin;
+	g_return_if_fail (PK_IS_PLUGIN (object));
+	plugin = PK_PLUGIN (object);
+
+	g_hash_table_unref (plugin->priv->data);
+
+	G_OBJECT_CLASS (pk_plugin_parent_class)->finalize (object);
+}
+
+/**
+ * pk_plugin_class_init:
+ **/
+static void
+pk_plugin_class_init (PkPluginClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = pk_plugin_finalize;
+	object_class->get_property = pk_plugin_get_property;
+	object_class->set_property = pk_plugin_set_property;
+
+	signals [SIGNAL_REFRESH] =
+		g_signal_new ("refresh",
+			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (PkPluginClass, refresh),
+			      NULL, NULL, g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+
+	pspec = g_param_spec_uint ("x", NULL, NULL,
+				   0, G_MAXUINT, 0,
+				   G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_X, pspec);
+
+	pspec = g_param_spec_uint ("y", NULL, NULL,
+				   0, G_MAXUINT, 0,
+				   G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_Y, pspec);
+
+	pspec = g_param_spec_uint ("width", NULL, NULL,
+				   0, G_MAXUINT, 0,
+				   G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_WIDTH, pspec);
+
+	pspec = g_param_spec_uint ("height", NULL, NULL,
+				   0, G_MAXUINT, 0,
+				   G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_HEIGHT, pspec);
+
+	pspec = g_param_spec_pointer ("display", NULL, NULL,
+				      G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_DISPLAY, pspec);
+
+	pspec = g_param_spec_pointer ("visual", NULL, NULL,
+				      G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_VISUAL, pspec);
+
+	pspec = g_param_spec_boolean ("started", NULL, NULL,
+				      FALSE,
+				      G_PARAM_READWRITE);
+	g_object_class_install_property (object_class, PROP_STARTED, pspec);
+
+	g_type_class_add_private (klass, sizeof (PkPluginPrivate));
+}
+
+/**
+ * pk_plugin_init:
+ **/
+static void
+pk_plugin_init (PkPlugin *plugin)
+{
+	plugin->priv = PK_PLUGIN_GET_PRIVATE (plugin);
+	plugin->priv->started = FALSE;
+	plugin->priv->x = 0;
+	plugin->priv->y = 0;
+	plugin->priv->width = 0;
+	plugin->priv->height = 0;
+	plugin->priv->display = NULL;
+	plugin->priv->visual = NULL;
+	plugin->priv->data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+}
+
+/**
+ * pk_plugin_new:
+ * Return value: A new plugin_install class instance.
+ **/
+PkPlugin *
+pk_plugin_new (void)
+{
+	PkPlugin *plugin;
+	plugin = g_object_new (PK_TYPE_PLUGIN, NULL);
+	return PK_PLUGIN (plugin);
+}
+
+/***************************************************************************
+ ***                          MAKE CHECK TESTS                           ***
+ ***************************************************************************/
+#ifdef EGG_TEST
+#include "egg-test.h"
+
+void
+egg_test_plugin_install (EggTest *test)
+{
+	PkPlugin *plugin;
+
+	if (!egg_test_start (test, "PkPlugin"))
+		return;
+
+	/************************************************************/
+	egg_test_title (test, "get an instance");
+	plugin = pk_plugin_new ();
+	if (plugin != NULL)
+		egg_test_success (test, NULL);
+	else
+		egg_test_failed (test, NULL);
+
+	g_object_unref (plugin);
+
+	egg_test_end (test);
+}
+#endif
+
diff --git a/contrib/browser-plugin/pk-plugin.h b/contrib/browser-plugin/pk-plugin.h
new file mode 100644
index 0000000..e97f0ee
--- /dev/null
+++ b/contrib/browser-plugin/pk-plugin.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#ifndef __PK_PLUGIN_H
+#define __PK_PLUGIN_H
+
+#include <glib-object.h>
+#include <cairo-xlib.h>
+
+G_BEGIN_DECLS
+
+#define PK_TYPE_PLUGIN		(pk_plugin_get_type ())
+#define PK_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), PK_TYPE_PLUGIN, PkPlugin))
+#define PK_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), PK_TYPE_PLUGIN, PkPluginClass))
+#define PK_IS_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), PK_TYPE_PLUGIN))
+#define PK_IS_PLUGIN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), PK_TYPE_PLUGIN))
+#define PK_PLUGIN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), PK_TYPE_PLUGIN, PkPluginClass))
+
+typedef struct PkPluginPrivate PkPluginPrivate;
+
+typedef struct
+{
+	GObject		 parent;
+	PkPluginPrivate	*priv;
+} PkPlugin;
+
+typedef struct
+{
+	GObjectClass	 parent_class;
+	/* signals */
+	void		 (*refresh)		(PkPlugin	*plugin);
+
+	/* vtable */
+	gboolean	 (*start)		(PkPlugin	*plugin);
+	gboolean	 (*draw)		(PkPlugin	*plugin,
+						 cairo_t	*cr);
+	gboolean	 (*button_press)	(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y,
+						 Time		 event_time);
+	gboolean	 (*button_release)	(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y,
+						 Time		 event_time);
+	gboolean	 (*motion)		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y);
+	gboolean	 (*enter)		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y);
+	gboolean	 (*leave)		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y);
+} PkPluginClass;
+
+GType		 pk_plugin_get_type		(void);
+PkPlugin	*pk_plugin_new			(void);
+gboolean	 pk_plugin_set_data		(PkPlugin	*plugin,
+						 const gchar	*name,
+						 const gchar	*value);
+const gchar	*pk_plugin_get_data		(PkPlugin	*plugin,
+						 const gchar	*name);
+gboolean	 pk_plugin_start		(PkPlugin	*plugin);
+gboolean	 pk_plugin_draw			(PkPlugin	*plugin,
+						 cairo_t	*cr);
+gboolean	 pk_plugin_button_press		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y,
+						 Time		 event_time);
+gboolean	 pk_plugin_button_release	(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y,
+						 Time		 event_time);
+gboolean	 pk_plugin_motion		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y);
+gboolean	 pk_plugin_enter		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y);
+gboolean	 pk_plugin_leave		(PkPlugin	*plugin,
+						 gint		 x,
+						 gint		 y);
+gboolean	 pk_plugin_request_refresh	(PkPlugin	*plugin);
+
+G_END_DECLS
+
+#endif /* __PK_PLUGIN_H */
diff --git a/contrib/browser-plugin/pk-store.c b/contrib/browser-plugin/pk-store.c
new file mode 100644
index 0000000..3671d04
--- /dev/null
+++ b/contrib/browser-plugin/pk-store.c
@@ -0,0 +1,175 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#include <config.h>
+
+#include <glib-object.h>
+
+#include "pk-main.h"
+#include "pk-store.h"
+
+#define PK_STORE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PK_TYPE_STORE, PkStorePrivate))
+
+struct PkStorePrivate
+{
+	GHashTable		*data;
+};
+
+G_DEFINE_TYPE (PkStore, pk_store, G_TYPE_OBJECT)
+
+/**
+ * pk_store_lookup_plugin:
+ **/
+PkPlugin *
+pk_store_lookup_plugin (PkStore	*store, NPP instance)
+{
+	PkPlugin *plugin;
+
+	g_return_val_if_fail (PK_IS_STORE (store), NULL);
+
+	/* find plugin for this instance */
+	plugin = g_hash_table_lookup (store->priv->data, instance);
+
+	return plugin;
+}
+
+/**
+ * pk_store_add_plugin:
+ **/
+gboolean
+pk_store_add_plugin (PkStore *store, NPP instance, PkPlugin *plugin)
+{
+	PkPlugin *plugin_tmp;
+	gboolean ret = TRUE;
+
+	/* check it's not already here */
+	plugin_tmp = pk_store_lookup_plugin (store, instance);
+	if (plugin_tmp != NULL) {
+		pk_warning ("already added plugin <%p> for instance [%p]", plugin_tmp, instance);
+		ret = FALSE;
+		goto out;
+	}
+
+	/* it's no, so add it */
+	pk_debug ("adding plugin <%p> for instance [%p]", plugin, instance);
+
+	g_hash_table_insert (store->priv->data, instance, g_object_ref (plugin));
+out:
+	return ret;
+}
+
+/**
+ * pk_store_remove_plugin:
+ **/
+gboolean
+pk_store_remove_plugin (PkStore *store, NPP instance)
+{
+	gboolean ret;
+
+	pk_debug ("removing plugin for instance [%p]", instance);
+
+	/* remove from hash (also unrefs) */
+	ret = g_hash_table_remove (store->priv->data, instance);
+	if (!ret) {
+		pk_warning ("nothing to remove for instance [%p]", instance);
+		goto out;
+	}
+out:
+	return ret;
+}
+
+/**
+ * pk_store_finalize:
+ **/
+static void
+pk_store_finalize (GObject *object)
+{
+	PkStore *store;
+	g_return_if_fail (PK_IS_STORE (object));
+	store = PK_STORE (object);
+
+	g_hash_table_unref (store->priv->data);
+
+	G_OBJECT_CLASS (pk_store_parent_class)->finalize (object);
+}
+
+/**
+ * pk_store_class_init:
+ **/
+static void
+pk_store_class_init (PkStoreClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = pk_store_finalize;
+
+	g_type_class_add_private (klass, sizeof (PkStorePrivate));
+}
+
+/**
+ * pk_store_init:
+ **/
+static void
+pk_store_init (PkStore *store)
+{
+	store->priv = PK_STORE_GET_PRIVATE (store);
+	store->priv->data = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+}
+
+/**
+ * pk_store_new:
+ * Return value: A new store_install class instance.
+ **/
+PkStore *
+pk_store_new (void)
+{
+	PkStore *store;
+	store = g_object_new (PK_TYPE_STORE, NULL);
+	return PK_STORE (store);
+}
+
+/***************************************************************************
+ ***                          MAKE CHECK TESTS                           ***
+ ***************************************************************************/
+#ifdef EGG_TEST
+#include "egg-test.h"
+
+void
+egg_test_store_install (EggTest *test)
+{
+	PkStore *store;
+
+	if (!egg_test_start (test, "PkStore"))
+		return;
+
+	/************************************************************/
+	egg_test_title (test, "get an instance");
+	store = pk_store_new ();
+	if (store != NULL)
+		egg_test_success (test, NULL);
+	else
+		egg_test_failed (test, NULL);
+
+	g_object_unref (store);
+
+	egg_test_end (test);
+}
+#endif
+
diff --git a/contrib/browser-plugin/pk-store.h b/contrib/browser-plugin/pk-store.h
new file mode 100644
index 0000000..3969a1b
--- /dev/null
+++ b/contrib/browser-plugin/pk-store.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009 Richard Hughes <richard at hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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.
+ */
+
+#ifndef __PK_STORE_H
+#define __PK_STORE_H
+
+#include <glib-object.h>
+#include <npapi.h>
+
+#include "pk-plugin.h"
+
+G_BEGIN_DECLS
+
+#define PK_TYPE_STORE		(pk_store_get_type ())
+#define PK_STORE(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), PK_TYPE_STORE, PkStore))
+#define PK_STORE_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), PK_TYPE_STORE, PkStoreClass))
+#define PK_IS_STORE(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), PK_TYPE_STORE))
+#define PK_IS_STORE_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), PK_TYPE_STORE))
+#define PK_STORE_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), PK_TYPE_STORE, PkStoreClass))
+
+typedef struct PkStorePrivate PkStorePrivate;
+
+typedef struct
+{
+	GObject		 parent;
+	PkStorePrivate	*priv;
+} PkStore;
+
+typedef struct
+{
+	GObjectClass	 parent_class;
+} PkStoreClass;
+
+GType		 pk_store_get_type		(void);
+PkStore		*pk_store_new			(void);
+gboolean	 pk_store_add_plugin		(PkStore	*store,
+						 NPP		 instance,
+						 PkPlugin	*plugin);
+gboolean	 pk_store_remove_plugin		(PkStore	*store,
+						 NPP		 instance);
+PkPlugin	*pk_store_lookup_plugin		(PkStore	*store,
+						 NPP		 instance);
+
+G_END_DECLS
+
+#endif /* __PK_STORE_H */
diff --git a/contrib/browser-plugin/sdk/npplat.h b/contrib/browser-plugin/sdk/npplat.h
deleted file mode 100644
index d426a88..0000000
--- a/contrib/browser-plugin/sdk/npplat.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef npplat_h_
-#define npplat_h_
-
-#include "npapi.h"
-#include "npfunctions.h"
-
-#ifdef XP_WIN
-#include "windows.h"
-#endif
-
-#ifdef XP_UNIX
-#include <stdio.h>
-#endif
-
-#ifdef XP_MAC
-#include <Carbon/Carbon.h>
-#endif
-
-#ifndef HIBYTE
-#define HIBYTE(i) (i >> 8)
-#endif
-
-#ifndef LOBYTE
-#define LOBYTE(i) (i & 0xff)
-#endif
-
-#endif // npplat_h_
diff --git a/contrib/browser-plugin/sdk/pluginbase.h b/contrib/browser-plugin/sdk/pluginbase.h
deleted file mode 100644
index 1168bca..0000000
--- a/contrib/browser-plugin/sdk/pluginbase.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef pluginbase_h_
-#define pluginbase_h_
-
-#include "npplat.h"
-
-struct nsPluginCreateData
-{
-  NPP instance;
-  NPMIMEType type; 
-  uint16_t mode; 
-  int16_t argc; 
-  char** argn; 
-  char** argv; 
-  NPSavedData* saved;
-};
-
-class nsPluginInstanceBase
-{
-public:
-  // these three methods must be implemented in the derived
-  // class platform specific way
-  virtual NPBool init(NPWindow* aWindow) = 0;
-  virtual void shut() = 0;
-  virtual NPBool isInitialized() = 0;
-
-  // implement all or part of those methods in the derived 
-  // class as needed
-  virtual NPError SetWindow(NPWindow* pNPWindow)                    { return NPERR_NO_ERROR; }
-  virtual NPError NewStream(NPMIMEType type, NPStream* stream, 
-                            NPBool seekable, uint16_t* stype)       { return NPERR_NO_ERROR; }
-  virtual NPError DestroyStream(NPStream *stream, NPError reason)   { return NPERR_NO_ERROR; }
-  virtual void    StreamAsFile(NPStream* stream, const char* fname) { return; }
-  virtual int32_t WriteReady(NPStream *stream)                      { return 0x0fffffff; }
-  virtual int32_t Write(NPStream *stream, int32_t offset, 
-                        int32_t len, void *buffer)                  { return len; }
-  virtual void    Print(NPPrint* printInfo)                         { return; }
-  virtual uint16_t HandleEvent(void* event)                         { return 0; }
-  virtual void    URLNotify(const char* url, NPReason reason, 
-                            void* notifyData)                       { return; }
-  virtual NPError GetValue(NPPVariable variable, void *value)       { return NPERR_NO_ERROR; }
-  virtual NPError SetValue(NPNVariable variable, void *value)       { return NPERR_NO_ERROR; }
-};
-
-// functions that should be implemented for each specific plugin
-
-// creation and destruction of the object of the derived class
-nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct);
-void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin);
-
-// global plugin initialization and shutdown
-NPError NS_PluginInitialize();
-void NS_PluginShutdown();
-
-#ifdef XP_UNIX
-// global to get plugins name & description 
-NPError NS_PluginGetValue(NPPVariable aVariable, void *aValue);
-#endif
-
-#endif // pluginbase_h_
diff --git a/contrib/browser-plugin/src/contents.cpp b/contrib/browser-plugin/src/contents.cpp
deleted file mode 100644
index 3de2b4a..0000000
--- a/contrib/browser-plugin/src/contents.cpp
+++ /dev/null
@@ -1,798 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is packagekit-plugin code.
- *
- * The Initial Developer of the Original Code is
- * Red Hat, Inc.
- * Portions created by the Initial Developer are Copyright (C) 2008
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#define MOZ_X11
-
-#include <config.h>
-
-#include <string.h>
-
-#include <glib/gi18n-lib.h>
-
-#include <cairo-xlib.h>
-#include <dlfcn.h>
-#include <pango/pangocairo.h>
-#include <gtk/gtk.h>
-#include <gdk/gdkx.h>
-#include <gio/gdesktopappinfo.h>
-#include <packagekit-glib/packagekit.h>
-
-#include "plugin.h"
-
-#define MARGIN 5
-
-#if !GTK_CHECK_VERSION(2,14,0)
-#define GTK_ICON_LOOKUP_FORCE_SIZE (GtkIconLookupFlags) 0
-#endif
-
-////////////////////////////////////////
-//
-// PkpContents class implementation
-//
-
-static std::vector<std::string>
-splitString(const gchar *str)
-{
-	std::vector<std::string> v;
-
-	if (str) {
-		char **split = g_strsplit(str, " ", -1);
-		for (char **s = split; *s; s++) {
-			char *stripped = strdup(*s);
-			g_strstrip(stripped);
-			v.push_back(stripped);
-			g_free(stripped);
-		}
-
-		g_strfreev(split);
-	}
-
-	return v;
-}
-
-PkpContents::PkpContents(const gchar *displayName, const gchar *packageNames) :
-	mPlugin(0),
-	mStatus(IN_PROGRESS),
-	mAppInfo(0),
-	mDisplayName(displayName),
-	mPackageNames(splitString(packageNames)),
-	mLayout(0),
-	mInstallPackageProxy(0),
-	mInstallPackageCall(0)
-{
-	recheck();
-}
-
-PkpContents::~PkpContents()
-{
-	clearLayout();
-
-	if (mAppInfo != 0) {
-		g_object_unref(mAppInfo);
-		mAppInfo = 0;
-	}
-
-	if (mInstallPackageCall != 0) {
-		dbus_g_proxy_cancel_call(mInstallPackageProxy, mInstallPackageCall);
-		g_object_unref(mInstallPackageProxy);
-		mInstallPackageProxy = 0;
-		mInstallPackageCall = 0;
-	}
-
-	while (!mClients.empty())
-		removeClient(mClients.front());
-}
-
-void PkpContents::recheck()
-{
-	mStatus = IN_PROGRESS;
-	mAvailableVersion = "";
-	mAvailablePackageName = "";
-	mInstalledPackageName = "";
-
-	for (std::vector<std::string>::iterator i = mPackageNames.begin(); i != mPackageNames.end(); i++) {
-		GError *error = NULL;
-		PkClient *client = pk_client_new();
-		gchar **package_ids;
-		package_ids = pk_package_ids_from_id (i->c_str());
-		if (!pk_client_resolve(client, PK_FILTER_ENUM_NONE, package_ids, &error)) {
-			g_warning("%s", error->message);
-			g_clear_error(&error);
-			g_object_unref(client);
-		} else {
-			g_signal_connect(client, "package", G_CALLBACK(onClientPackage), this);
-			g_signal_connect(client, "error-code", G_CALLBACK(onClientErrorCode), this);
-			g_signal_connect(client, "finished", G_CALLBACK(onClientFinished), this);
-			mClients.push_back(client);
-		}
-		g_strfreev (package_ids);
-	}
-
-	if (mClients.empty() && getStatus() == IN_PROGRESS)
-		setStatus(UNAVAILABLE);
-}
-
-void PkpContents::removeClient(PkClient *client)
-{
-	for (std::vector<PkClient *>::iterator i = mClients.begin(); i != mClients.end(); i++) {
-		if (*i == client) {
-			mClients.erase(i);
-			g_signal_handlers_disconnect_by_func(client, (void *)onClientPackage, this);
-			g_signal_handlers_disconnect_by_func(client, (void *)onClientErrorCode, this);
-			g_signal_handlers_disconnect_by_func(client, (void *)onClientFinished, this);
-			g_object_unref(client);
-			break;
-		}
-	}
-
-	if (mClients.empty()) {
-		if (getStatus() == IN_PROGRESS)
-			setStatus(UNAVAILABLE);
-	}
-}
-
-void
-PkpContents::setStatus(PackageStatus status)
-{
-	if (mStatus != status) {
-		mStatus = status;
-		clearLayout();
-		refresh();
-	}
-}
-
-void
-PkpContents::setAvailableVersion(const gchar *version)
-{
-	mAvailableVersion = version;
-	clearLayout();
-	refresh();
-}
-
-void
-PkpContents::setAvailablePackageName(const gchar *name)
-{
-	mAvailablePackageName = name;
-}
-
-void
-PkpContents::setInstalledPackageName(const gchar *name)
-{
-	mInstalledPackageName = name;
-}
-
-void
-PkpContents::setInstalledVersion(const gchar *version)
-{
-	mInstalledVersion = version;
-	clearLayout();
-	refresh();
-}
-
-void
-PkpContents::clearLayout()
-{
-	if (mLayout) {
-		g_object_unref(mLayout);
-		mLayout = 0;
-	}
-}
-
-static void
-append_markup(GString *str, const gchar *format, ...)
-{
-	va_list vap;
-
-	va_start(vap, format);
-	char *tmp = g_markup_vprintf_escaped(format, vap);
-	va_end(vap);
-
-	g_string_append(str, tmp);
-	g_free(tmp);
-}
-
-static guint32
-rgba_from_gdk_color(GdkColor *color)
-{
-	return (((color->red >> 8) << 24) |
-		((color->green >> 8) << 16) |
-		((color->blue >> 8) << 8) |
-		0xff);
-}
-
-static void
-set_source_from_rgba(cairo_t *cr, guint32 rgba)
-{
-	cairo_set_source_rgba(cr,
-			 ((rgba & 0xff000000) >> 24) / 255.,
-			 ((rgba & 0x00ff0000) >> 16) / 255.,
-			 ((rgba & 0x0000ff00) >> 8) / 255.,
-			  (rgba & 0x000000ff) / 255.);
-
-}
-
-/* Retrieve the system colors and fonts.
- * This looks incredibly expensive .... to create a GtkWindow for
- * every expose ... but actually it's only moderately expensive;
- * Creating a GtkWindow is just normal GObject creation overhead --
- * the extra expense beyond that will come when we actually create
- * the window.
- */
-static void
-get_style(PangoFontDescription **font_desc, guint32 *foreground, guint32 *background, guint32 *link)
-{
-	GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	GtkStyle *style;
-	GdkColor link_color = { 0, 0, 0, 0xeeee };
-	GdkColor *tmp = NULL;
-
-	gtk_widget_ensure_style(window);
-
-	style = gtk_widget_get_style(window);
-	*foreground = rgba_from_gdk_color(&style->text[GTK_STATE_NORMAL]);
-	*background = rgba_from_gdk_color(&style->base[GTK_STATE_NORMAL]);
-
-	gtk_widget_style_get (GTK_WIDGET (window), "link-color", &tmp, NULL);
-	if (tmp != NULL) {
-		link_color = *tmp;
-		gdk_color_free(tmp);
-	}
-
-	*link = rgba_from_gdk_color(&link_color);
-
-	*font_desc = pango_font_description_copy(style->font_desc);
-
-	gtk_widget_destroy(window);
-}
-
-void
-PkpContents::ensureLayout(cairo_t *cr, PangoFontDescription *font_desc, guint32 link_color)
-{
-	GString *markup = g_string_new(NULL);
-
-	if (mLayout)
-		return;
-
-	mLayout = pango_cairo_create_layout(cr);
-	pango_layout_set_font_description(mLayout, font_desc);
-
-	/* WARNING: Any changes to what links are created here will require corresponding
-	 * changes to the buttonRelease() method
-	 */
-	switch (mStatus) {
-	case IN_PROGRESS:
-		/* TRANSLATORS: when we are getting data from the daemon */
-		append_markup(markup, _("Getting package information..."));
-		break;
-	case INSTALLED:
-		if (mAppInfo != 0) {
-			append_markup(markup, "<span color='#%06x' underline='single'>", link_color >> 8);
-			/* TRANSLATORS: run an applicaiton */
-			append_markup(markup, _("Run %s"), mDisplayName.c_str());
-			append_markup(markup, "</span>");
-		} else
-			append_markup(markup, "<big>%s</big>", mDisplayName.c_str());
-		if (!mInstalledVersion.empty())
-			/* TRANSLATORS: show the installed version of a package */
-			append_markup(markup, "\n<small>%s: %s</small>", _("Installed version"), mInstalledVersion.c_str());
-		break;
-	case UPGRADABLE:
-		append_markup(markup, "<big>%s</big>", mDisplayName.c_str());
-		if (mAppInfo != 0) {
-			if (!mInstalledVersion.empty()) {
-				append_markup(markup, "\n<span color='#%06x' underline='single'>", link_color >> 8);
-				/* TRANSLATORS: run the application now */
-				append_markup(markup, _("Run version %s now"), mInstalledVersion.c_str());
-				append_markup(markup, "</span>");
-			} else {
-				append_markup(markup,
-				              "\n<span color='#%06x' underline='single'>%s</span>",
-					      /* TRANSLATORS: run the application now */
-					      _("Run now"), link_color >> 8);
-		        }
-		}
-
-		append_markup(markup, "\n<span color='#%06x' underline='single'>", link_color >> 8);
-		/* TRANSLATORS: update to a new version of the package */
-		append_markup(markup, _("Update to version %s"), mAvailableVersion.c_str());
-		append_markup(markup, "</span>");
-		break;
-	case AVAILABLE:
-		append_markup(markup, "<span color='#%06x' underline='single'>", link_color >> 8);
-		/* TRANSLATORS: To install a package */
-		append_markup(markup, _("Install %s now"), mDisplayName.c_str());
-		append_markup(markup, "</span>");
-		/* TRANSLATORS: the version of the package */
-		append_markup(markup, "\n<small>%s: %s</small>", _("Version"), mAvailableVersion.c_str());
-		break;
-	case UNAVAILABLE:
-		append_markup(markup, "<big>%s</big>", mDisplayName.c_str());
-		/* TRANSLATORS: noting found, so can't install */
-		append_markup(markup, "\n<small>%s</small>", _("No packages found for your system"));
-		break;
-	case INSTALLING:
-		append_markup(markup, "<big>%s</big>", mDisplayName.c_str());
-		/* TRANSLATORS: package is being installed */
-		append_markup(markup, "\n<small>%s</small>", _("Installing..."));
-		break;
-	}
-
-	pango_layout_set_markup(mLayout, markup->str, -1);
-	g_string_free(markup, TRUE);
-}
-
-void
-PkpContents::refresh()
-{
-	if (mPlugin != 0)
-		mPlugin->refresh();
-}
-
-void
-PkpContents::setPlugin(PkpPluginInstance *plugin)
-{
-	mPlugin = plugin;
-}
-
-gchar *
-PkpContents::getBestDesktopFile()
-{
-	GPtrArray *array = NULL;
-	PkDesktop *desktop;
-	gboolean ret;
-	gchar *data = NULL;
-	const gchar *package;
-
-	/* open desktop database */
-	desktop = pk_desktop_new();
-	ret = pk_desktop_open_database(desktop, NULL);
-	if (!ret)
-		goto out;
-
-	/* get files */
-	package = mInstalledPackageName.c_str();
-	array = pk_desktop_get_shown_for_package(desktop, package, NULL);
-	if (array == NULL)
-		goto out;
-	if (array->len == 0)
-		goto out;
-
-	/* just use the first entry */
-	data = g_strdup((const gchar*) g_ptr_array_index(array, 0));
-
-out:
-	if (array != NULL) {
-		g_ptr_array_foreach(array, (GFunc) g_free, NULL);
-		g_ptr_array_free (array, TRUE);
-	}
-	g_object_unref(desktop);
-	return data;
-}
-
-gchar *
-PkpContents::getPackageIcon()
-{
-	gboolean ret;
-	GKeyFile *file;
-	gchar *data = NULL;
-	const gchar *filename;
-
-	/* get data from the best file */
-	file = g_key_file_new();
-	filename = getBestDesktopFile();
-	if (filename == NULL)
-		goto out;
-
-	ret = g_key_file_load_from_file(file, filename, G_KEY_FILE_NONE, NULL);
-	if (!ret) {
-		g_warning("failed to open %s", filename);
-		goto out;
-	}
-	data = g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
-	g_key_file_free(file);
-out:
-	return data;
-}
-
-void
-PkpContents::draw(cairo_t *cr)
-{
-	guint32 foreground, background, link;
-	PangoFontDescription *font_desc;
-	guint x = mPlugin->getX();
-	guint y = mPlugin->getY();
-	cairo_surface_t *surface = NULL;
-	const gchar *filename;
-	GtkIconTheme *theme;
-	GdkPixbuf *pixbuf;
-
-	/* get properties */
-	get_style(&font_desc, &foreground, &background, &link);
-
-        /* fill background */
-	set_source_from_rgba(cr, background);
-	cairo_rectangle(cr, x, y, mPlugin->getWidth(), mPlugin->getHeight());
-	cairo_fill(cr);
-
-        /* grey outline */
-	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
-	cairo_rectangle(cr, x + 0.5, y + 0.5, mPlugin->getWidth() - 1, mPlugin->getHeight() - 1);
-	cairo_set_line_width(cr, 1);
-	cairo_stroke(cr);
-
-
-	/* get themed icon */
-	filename = getPackageIcon();
-	if (filename == NULL)
-		filename = "package-x-generic";
-	theme = gtk_icon_theme_get_default();
-	pixbuf = gtk_icon_theme_load_icon(theme, filename, 48, GTK_ICON_LOOKUP_FORCE_SIZE, NULL);
-	if (pixbuf == NULL)
-		goto skip;
-	gdk_cairo_set_source_pixbuf(cr, pixbuf, x + MARGIN, y + MARGIN);
-	cairo_rectangle(cr, x + MARGIN, y + MARGIN, 48, 48);
-	cairo_fill(cr);
-	cairo_surface_destroy(surface);
-	g_object_unref(pixbuf);
-
-skip:
-	/* write text */
-	ensureLayout(cr, font_desc, link);
-	cairo_move_to(cr,(x + MARGIN*2) + 48, y + MARGIN + MARGIN);
-	set_source_from_rgba(cr, foreground);
-	pango_cairo_show_layout(cr, mLayout);
-}
-
-/* Cut and paste from pango-layout.c; determines if a layout iter is on
- * a line terminated by a real line break (rather than a line break from
- * wrapping). We use this to determine whether the empty run at the end
- * of a display line should be counted as a break between links or not.
- *
- * (Code in pango-layout.c is by me, Copyright Red Hat, and hereby relicensed
- * to the license of this file)
- */
-static gboolean
-line_is_terminated (PangoLayoutIter *iter)
-{
-	/* There is a real terminator at the end of each paragraph other
-	 * than the last.
-	 */
-	PangoLayoutLine *line = pango_layout_iter_get_line(iter);
-	GSList *lines = pango_layout_get_lines(pango_layout_iter_get_layout(iter));
-	GSList *link = g_slist_find(lines, line);
-	if (!link) {
-		g_warning("Can't find line in layout line list\n");
-		return FALSE;
-	}
-
-	if (link->next) {
-		PangoLayoutLine *next_line = (PangoLayoutLine *)link->next->data;
-		if (next_line->is_paragraph_start)
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
-/* This function takes an X,Y position and determines whether it is over one
- * of the underlined portions of the layout (a link). It works by iterating
- * through the runs of the layout (a run is a segment with a consistent
- * font and display attributes, more or less), and counting the underlined
- * segments that we see. A segment that is underlined could be broken up
- * into multiple runs if it is drawn with multiple fonts due to fonts
- * substitution, so we actually count non-underlined => underlined
- * transitions.
- */
-int
-PkpContents::getLinkIndex(int x, int y)
-{
-	/* Coordinates are relative to origin of plugin (different from drawing) */
-
-	if (!mLayout)
-		return -1;
-
-	x -= (MARGIN * 2) + 48;
-	y -= (MARGIN * 2);
-
-	int index;
-	int trailing;
-	if (!pango_layout_xy_to_index(mLayout, x * PANGO_SCALE, y * PANGO_SCALE, &index, &trailing))
-		return - 1;
-
-	PangoLayoutIter *iter = pango_layout_get_iter(mLayout);
-	int seen_links = 0;
-	bool in_link = false;
-	int result = -1;
-
-	while (TRUE) {
-		PangoLayoutRun *run = pango_layout_iter_get_run(iter);
-		if (run) {
-			PangoItem *item = run->item;
-			PangoUnderline uline = PANGO_UNDERLINE_NONE;
-
-			for (GSList *l = item->analysis.extra_attrs; l; l = l->next) {
-				PangoAttribute *attr = (PangoAttribute *)l->data;
-				if (attr->klass->type == PANGO_ATTR_UNDERLINE) {
-					uline = (PangoUnderline)((PangoAttrInt *)attr)->value;
-				}
-			}
-
-			if (uline == PANGO_UNDERLINE_NONE)
-				in_link = FALSE;
-			else if (!in_link) {
-				in_link = TRUE;
-				seen_links++;
-			}
-
-			if (item->offset <= index && index < item->offset + item->length) {
-				if (in_link)
-					result = seen_links - 1;
-
-				break;
-			}
-		} else {
-			/* We have an empty run at the end of each line. A line break doesn't
-			 * terminate the link, but a real newline does.
-			 */
-			if (line_is_terminated(iter))
-				in_link = FALSE;
-		}
-
-		if (!pango_layout_iter_next_run (iter))
-			break;
-	}
-
-	pango_layout_iter_free(iter);
-
-	return result;
-}
-
-void
-PkpContents::buttonPress(int x, int y, Time time)
-{
-}
-
-void
-PkpContents::buttonRelease(int x, int y, Time time)
-{
-	int index = getLinkIndex(x, y);
-	if (index < 0)
-		return;
-
-	switch (mStatus) {
-	case IN_PROGRESS:
-	case INSTALLING:
-	case UNAVAILABLE:
-		break;
-	case INSTALLED:
-		if (mAppInfo != 0)
-			runApplication(time);
-		break;
-	case UPGRADABLE:
-		if (mAppInfo != 0 && index == 0)
-			runApplication(time);
-		else
-			installPackage(time);
-		break;
-	case AVAILABLE:
-		if (!mAvailablePackageName.empty())
-			installPackage(time);
-		break;
-	}
-}
-
-void
-PkpContents::motion(int x, int y)
-{
-}
-
-void
-PkpContents::enter(int x, int y)
-{
-}
-
-void
-PkpContents::leave(int x, int y)
-{
-}
-
-static guint32
-get_server_timestamp()
-{
-	GtkWidget *invisible = gtk_invisible_new();
-	GdkWindow *window;
-	guint32 server_time;
-
-	gtk_widget_realize(invisible);
-	window = gtk_widget_get_window(invisible);
-	server_time = gdk_x11_get_server_time(window);
-	gtk_widget_destroy(invisible);
-	return server_time;
-}
-
-void
-PkpContents::runApplication (Time time)
-{
-	GError *error = NULL;
-	GdkAppLaunchContext *context;
-
-	if (mAppInfo == 0) {
-		g_warning("Didn't find application to launch");
-		return;
-	}
-
-	if (time == 0)
-		time = get_server_timestamp();
-
-	context = gdk_app_launch_context_new();
-	gdk_app_launch_context_set_timestamp(context, time);
-	if (!g_app_info_launch(mAppInfo, NULL, G_APP_LAUNCH_CONTEXT (context), &error)) {
-		g_warning("%s\n", error->message);
-		g_clear_error(&error);
-		return;
-	}
-
-	if (context != NULL)
-		g_object_unref(context);
-}
-
-void
-PkpContents::installPackage (Time time)
-{
-	GdkEvent *event;
-	GdkWindow *window;
-	guint xid = 0;
-
-	if (mAvailablePackageName.empty()) {
-		g_warning("No available package to install");
-		return;
-	}
-
-	if (mInstallPackageCall != 0) {
-		g_warning("Already installing package");
-		return;
-	}
-
-	/* Get a proxy to the *session* PackageKit service */
-	DBusGConnection *connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
-	mInstallPackageProxy = dbus_g_proxy_new_for_name(connection,
-							 "org.freedesktop.PackageKit",
-							 "/org/freedesktop/PackageKit",
-							 "org.freedesktop.PackageKit");
-
-	/* will be NULL when activated not using a keyboard or a mouse */
-	event = gtk_get_current_event();
-	if (event != NULL && event->any.window != NULL) {
-		window = gdk_window_get_toplevel(event->any.window);
-		xid = GDK_DRAWABLE_XID(window);
-	}
-
-	mInstallPackageCall = dbus_g_proxy_begin_call_with_timeout(mInstallPackageProxy,
-								   "InstallPackageName",
-								   onInstallPackageFinished,
-								   this,
-								   (GDestroyNotify)0,
-								   24 * 60 * 1000 * 1000, /* one day */
-								   G_TYPE_UINT, xid, /* xid */
-								   G_TYPE_UINT, 0, /* timespec */
-								   G_TYPE_STRING, mAvailablePackageName.c_str(),
-								   G_TYPE_INVALID,
-								   G_TYPE_INVALID);
-
-	 setStatus(INSTALLING);
-}
-
-void
-PkpContents::onClientPackage(PkClient *client, const PkPackageObj *obj, PkpContents *contents)
-{
-	gchar *filename;
-
-	/* if we didn't use displayname, use the summary */
-	if (contents->mDisplayName.size() == 0)
-		contents->mDisplayName = obj->summary;
-
-	/* parse the data */
-	if (obj->info == PK_INFO_ENUM_AVAILABLE) {
-		if (contents->getStatus() == IN_PROGRESS)
-			contents->setStatus(AVAILABLE);
-		else if (contents->getStatus() == INSTALLED)
-			contents->setStatus(UPGRADABLE);
-		contents->setAvailableVersion(obj->id->version);
-		contents->setAvailablePackageName(obj->id->name);
-
-#if 0
-		/* if we have data from the repo, override the user:
-		 *  * we don't want the remote site pretending to install another package
-		 *  * it might be localised if the backend supports it */
-		if (obj->summary != NULL && obj->summary[0] != '\0')
-			contents->mDisplayName = obj->summary;
-#endif
-
-	} else if (obj->info == PK_INFO_ENUM_INSTALLED) {
-		if (contents->getStatus() == IN_PROGRESS)
-			contents->setStatus(INSTALLED);
-		else if (contents->getStatus() == AVAILABLE)
-			contents->setStatus(UPGRADABLE);
-		contents->setInstalledVersion(obj->id->version);
-		contents->setInstalledPackageName(obj->id->name);
-
-		/* get desktop file information */
-		filename = contents->getBestDesktopFile();
-		if (filename != NULL) {
-			contents->mAppInfo = G_APP_INFO(g_desktop_app_info_new_from_filename(filename));
-#if 0
-			/* override, as this will have translation */
-			contents->mDisplayName = g_app_info_get_name(contents->mAppInfo);
-#endif
-		}
-		g_free(filename);
-
-		if (contents->mAppInfo != 0)
-			contents->setStatus(INSTALLED);
-	}
-}
-
-void
-PkpContents::onClientErrorCode(PkClient *client, PkErrorCodeEnum code, const gchar *details, PkpContents *contents)
-{
-	g_warning("Error getting data from PackageKit: %s\n", details);
-	contents->removeClient(client);
-}
-
-void
-PkpContents::onClientFinished(PkClient *client, PkExitEnum exit, guint runtime, PkpContents *contents)
-{
-	contents->removeClient(client);
-}
-
-void
-PkpContents::onInstallPackageFinished (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
-{
-	PkpContents *contents = (PkpContents *)user_data;
-
-	GError *error = NULL;
-	if (!dbus_g_proxy_end_call(proxy, call, &error, G_TYPE_INVALID)) {
-		g_warning("Error occurred during install: %s", error->message);
-		g_clear_error(&error);
-	}
-
-	g_object_unref(contents->mInstallPackageProxy);
-	contents->mInstallPackageProxy = 0;
-	contents->mInstallPackageCall = 0;
-
-	contents->recheck();
-}
diff --git a/contrib/browser-plugin/src/contents.h b/contrib/browser-plugin/src/contents.h
deleted file mode 100644
index 40d45c8..0000000
--- a/contrib/browser-plugin/src/contents.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is packagekit-plugin code.
- *
- * The Initial Developer of the Original Code is
- * Red Hat, Inc.
- * Portions created by the Initial Developer are Copyright (C) 2008
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-
-#ifndef __CONTENTS_H__
-#define __CONTENTS_H__
-
-#include <X11/Xlib.h>
-#include <gio/gio.h>
-#include <pango/pango.h>
-#include <packagekit-glib/packagekit.h>
-#include <cairo.h>
-#include <dbus/dbus-glib.h>
-#include <gtk/gtk.h>
-
-#include <string>
-#include <vector>
-
-class PkpPluginInstance;
-
-enum PackageStatus {
-	IN_PROGRESS, /* Looking up package information */
-	INSTALLED,   /* Package installed */
-	UPGRADABLE,  /* Package installed, newer version available */
-	AVAILABLE,   /* Package not installed, version available */
-	UNAVAILABLE, /* Package not installed or available */
-	INSTALLING   /* Currently installing a new version */
-};
-
-class PkpContents
-{
-public:
-	PkpContents(const gchar *displayName, const gchar *packageNames);
-	virtual ~PkpContents();
-
-	void setPlugin(PkpPluginInstance *plugin);
-
-	void draw(cairo_t *cr);
-	void buttonPress(int x, int y, Time time);
-	void buttonRelease(int x, int y, Time time);
-	void motion(int x, int y);
-	void enter(int x, int y);
-	void leave(int x, int y);
-
-private:
-	void recheck();
-	void findAppInfo();
-	void runApplication(Time time);
-	void installPackage(Time time);
-
-	int getLinkIndex(int x, int y);
-
-	void setStatus(PackageStatus status);
-	PackageStatus getStatus() { return mStatus; }
-	void setAvailableVersion(const gchar *version);
-	void setAvailablePackageName(const gchar *name);
-	void setInstalledVersion(const gchar *version);
-	void setInstalledPackageName(const gchar *name);
-
-	void ensureLayout(cairo_t *cr, PangoFontDescription *font_desc, guint32 link_color);
-	void clearLayout();
-	void refresh();
-	gchar *getPackageIcon();
-	gchar *getBestDesktopFile();
-
-	void removeClient(PkClient *client);
-
-	static void onClientPackage(PkClient *client, const PkPackageObj *obj, PkpContents *contents);
-	static void onClientErrorCode(PkClient *client, PkErrorCodeEnum code, const gchar *details, PkpContents *contents);
-	static void onClientFinished(PkClient *client, PkExitEnum exit, guint runtime, PkpContents *contents);
-	static void onInstallPackageFinished(DBusGProxy *proxy, DBusGProxyCall *call, void *user_data);
-
-	PkpPluginInstance *mPlugin;
-	PackageStatus mStatus;
-	std::string mAvailableVersion;
-	std::string mAvailablePackageName;
-	std::string mInstalledVersion;
-	std::string mInstalledPackageName;
-	GAppInfo *mAppInfo;
-
-	std::string mDisplayName;
-	std::vector<std::string> mPackageNames;
-
-	PangoLayout *mLayout;
-
-	std::vector<PkClient *> mClients;
-
-	DBusGProxy *mInstallPackageProxy;
-	DBusGProxyCall *mInstallPackageCall;
-};
-
-#endif // __CONTENTS_H__
diff --git a/contrib/browser-plugin/src/plugin.cpp b/contrib/browser-plugin/src/plugin.cpp
deleted file mode 100644
index e0aef5b..0000000
--- a/contrib/browser-plugin/src/plugin.cpp
+++ /dev/null
@@ -1,296 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Red Hat, Inc.
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#define MOZ_X11
-
-#include <config.h>
-
-#include <string.h>
-
-#include <glib/gi18n-lib.h>
-
-#include <cairo-xlib.h>
-#include <dlfcn.h>
-
-#include "plugin.h"
-
-#define MIME_TYPES_DESCRIPTION  "application/x-packagekit-plugin:bsc:PackageKit Plugin"
-#define PLUGIN_NAME             "PackageKit"
-#define PLUGIN_DESCRIPTION      "Plugin for Installing Applications"
-
-char* NP_GetMIMEDescription(void)
-{
-	return (char *)(MIME_TYPES_DESCRIPTION);
-}
-
-static void *module_handle = 0;
-
-/////////////////////////////////////
-// general initialization and shutdown
-//
-
-/* If our dependent libraries like libpackagekit get unloaded, bad stuff
- * happens (they may have registered GLib types and so forth) so we need
- * to keep them around. The (GNU extension) RTLD_NODELETE seems useful
- * but isn't so much, since it only refers to a specific library and not
- * its dependent libraries, so we'd have to identify specifically each
- * of our dependencies that is not safe to unload and that is most of
- * the GTK+ stack.
- */
-static void
-make_module_resident()
-{
-	Dl_info info;
-
-	/* Get the (absolute) filename of this module */
-	if (!dladdr((void *)NP_GetMIMEDescription, &info)) {
-		g_warning("Can't find filename for module");
-		return;
-	}
-
-	/* Now reopen it to get our own handle */
-	module_handle = dlopen(info.dli_fname, RTLD_NOW);
-	if (!module_handle) {
-		g_warning("Can't permanently open module %s", dlerror());
-		return;
-	}
-
-	/* the module will never be closed */
-}
-
-NPError NS_PluginInitialize()
-{
-	if (module_handle != 0) /* Already initialized */
-		return NPERR_NO_ERROR;
-
-	make_module_resident();
-
-#ifdef ENABLE_NLS
-	bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
-	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
-#endif
-
-	return NPERR_NO_ERROR;
-}
-
-void NS_PluginShutdown()
-{
-}
-
-// get values per plugin
-NPError NS_PluginGetValue(NPPVariable aVariable, void *aValue)
-{
-	NPError err = NPERR_NO_ERROR;
-	switch (aVariable) {
-	case NPPVpluginNameString:
-		*((char **)aValue) = (char *)PLUGIN_NAME;
-		break;
-	case NPPVpluginDescriptionString:
-		*((char **)aValue) = (char *)PLUGIN_DESCRIPTION;
-		break;
-	default:
-		err = NPERR_INVALID_PARAM;
-		break;
-	}
-	return err;
-}
-
-/////////////////////////////////////////////////////////////
-//
-// construction and destruction of our plugin instance object
-//
-nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
-{
-	const gchar *displayName = "";
-	const gchar *packageNames = NULL;
-
-	if(!aCreateDataStruct)
-		return NULL;
-
-	for (int i = 0; i < aCreateDataStruct->argc; i++) {
-		if (strcmp(aCreateDataStruct->argn[i], "displayname") == 0)
-			displayName = aCreateDataStruct->argv[i];
-		else if (strcmp(aCreateDataStruct->argn[i], "packagenames") == 0)
-			packageNames = aCreateDataStruct->argv[i];
-	}
-
-	PkpPluginInstance * plugin = new PkpPluginInstance(aCreateDataStruct->instance, displayName, packageNames);
-
-	NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)FALSE);
-
-	return plugin;
-}
-
-void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
-{
-	if(aPlugin)
-		delete (PkpPluginInstance *)aPlugin;
-}
-
-////////////////////////////////////////
-//
-// nsPluginInstance class implementation
-//
-
-PkpPluginInstance::PkpPluginInstance(NPP aInstance, const gchar *displayName, const gchar *packageNames) :
-	nsPluginInstanceBase(),
-	mInstance(aInstance),
-	mInitialized(FALSE),
-	mContents(displayName, packageNames),
-	mWindow(0)
-{
-	mContents.setPlugin(this);
-}
-
-PkpPluginInstance::~PkpPluginInstance()
-{
-}
-
-NPBool PkpPluginInstance::init(NPWindow* aWindow)
-{
-	if(aWindow == NULL)
-		return FALSE;
-
-	if (SetWindow(aWindow))
-		mInitialized = TRUE;
-	
-	return mInitialized;
-}
-
-void PkpPluginInstance::shut()
-{
-	mInitialized = FALSE;
-}
-
-NPError PkpPluginInstance::GetValue(NPPVariable aVariable, void *aValue)
-{
-	NPError err = NPERR_NO_ERROR;
-	switch (aVariable) {
-	case NPPVpluginNameString:
-	case NPPVpluginDescriptionString:
-		return NS_PluginGetValue(aVariable, aValue) ;
-		break;
-	default:
-		err = NPERR_INVALID_PARAM;
-		break;
-	}
-	return err;
-
-}
-
-NPError PkpPluginInstance::SetWindow(NPWindow* aWindow)
-{
-	if (aWindow == NULL)
-		return FALSE;
-
-	mX = aWindow->x;
-	mY = aWindow->y;
-	mWidth = aWindow->width;
-	mHeight = aWindow->height;
-
-	mWindow = (Window) aWindow->window;
-	NPSetWindowCallbackStruct *ws_info = (NPSetWindowCallbackStruct *)aWindow->ws_info;
-	mDisplay = ws_info->display;
-	mVisual = ws_info->visual;
-	mDepth = ws_info->depth;
-	mColormap = ws_info->colormap;
-
-	return NPERR_NO_ERROR;
-}
-
-void
-PkpPluginInstance::refresh()
-{
-	NPRect rect;
-
-	/* Coordinates here are relative to the plugin's origin (mX,mY) */
-
-	rect.left = 0;
-	rect.right =  mWidth;
-	rect.top = 0;
-	rect.bottom = mHeight;
-
-	NPN_InvalidateRect(mInstance, &rect);
-}
-
-uint16_t
-PkpPluginInstance::HandleEvent(void *event)
-{
-	XEvent *xev = (XEvent *)event;
-	cairo_surface_t *surface;
-	cairo_t *cr;
-	XButtonEvent *xbe;
-	XGraphicsExposeEvent *xge;
-	XMotionEvent *xme;
-	XCrossingEvent *xce;
-
-	switch (xev->xany.type) {
-	case GraphicsExpose:
-		xge = (XGraphicsExposeEvent *)event;
-		surface = cairo_xlib_surface_create (mDisplay, xge->drawable, mVisual, mX + mWidth, mY + mHeight);
-		cr = cairo_create(surface);
-		cairo_rectangle(cr, xge->x, xge->y, xge->width, xge->height);
-		cairo_clip(cr);
-		mContents.draw(cr);
-		cairo_destroy(cr);
-		cairo_surface_destroy(surface);
-		return 1;
-	case ButtonPress:
-		xbe = (XButtonEvent *)event;
-		mContents.buttonPress(xbe->x, xbe->y, xbe->time);
-		return 1;
-	case ButtonRelease:
-		xbe = (XButtonEvent *)event;
-		mContents.buttonRelease(xbe->x, xbe->y, xbe->time);
-		return 1;
-	case MotionNotify:
-		xme = (XMotionEvent *)event;
-		mContents.motion(xme->x, xme->y);
-		return 1;
-	case EnterNotify:
-		xce = (XCrossingEvent *)event;
-		mContents.enter(xce->x, xce->y);
-		return 1;
-	case LeaveNotify:
-		xce = (XCrossingEvent *)event;
-		mContents.leave(xce->x, xce->y);
-		return 1;
-	}
-	return 0;
-}
diff --git a/contrib/browser-plugin/src/plugin.h b/contrib/browser-plugin/src/plugin.h
deleted file mode 100644
index 28c64fd..0000000
--- a/contrib/browser-plugin/src/plugin.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef __PLUGIN_H__
-#define __PLUGIN_H__
-
-#include <X11/Xlib.h>
-
-#include "contents.h"
-#include "pluginbase.h"
-
-class PkpPluginInstance : public nsPluginInstanceBase
-{
-public:
-	PkpPluginInstance(NPP aInstance, const gchar *displayName, const gchar *packageNames);
-	virtual ~PkpPluginInstance();
-
-	NPBool init(NPWindow* aWindow);
-	void shut();
-	NPBool isInitialized() {return mInitialized;}
-	NPError GetValue(NPPVariable variable, void *value);
-	NPError SetWindow(NPWindow* aWindow);
-	uint16_t HandleEvent(void *event);
-
-	void refresh();
-
-	int getX() { return mX; }
-	int getY() { return mY; }
-	int getWidth() { return mWidth; }
-	int getHeight() { return mHeight; }
-
-private:
-	NPP mInstance;
-	NPBool mInitialized;
-
-	PkpContents mContents;
-
-	Window mWindow;
-	Display *mDisplay;
-	int mX, mY;
-	int mWidth, mHeight;
-	Visual* mVisual;
-	Colormap mColormap;
-	unsigned int mDepth;
-};
-
-#endif // __PLUGIN_H__
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2e0113f..19416f4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,7 +6,7 @@ client/pk-generate-pack.c
 client/pk-monitor.c
 client/pk-tools-common.c
 contrib/command-not-found/pk-command-not-found.c
-contrib/browser-plugin/src/contents.cpp
+contrib/browser-plugin/pk-plugin-install.c
 contrib/debuginfo-install/pk-debuginfo-install.c
 data/packagekit-catalog.xml.in
 data/packagekit-package-list.xml.in
commit 4207054cc3b4432a42a165a1454567c2bc040aa1
Author: Richard Hughes <richard at hughsie.com>
Date:   Tue Jul 14 21:36:24 2009 +0100

    Add some more notes from David about PolicyKit internals

diff --git a/docs/security.txt b/docs/security.txt
index 3f4bd4b..f5a3ef8 100644
--- a/docs/security.txt
+++ b/docs/security.txt
@@ -32,7 +32,8 @@ This may involve the user authenticating that they are either the user (by
 typing their password) or that they are an administrative user (by typing the
 root password or the password of a user designated as an administrative user).
 The authorization check can take some time, but the daemon can process other
-requests whilst waiting for user input.
+requests whilst waiting for user input. Please see the the PolicyKit-1 man page
+for more information.
 
 The packagekitd daemon is started using D-Bus system activation, which means it
 is started without any environment (no PATH, etc) and therefore is impossible to
@@ -46,7 +47,23 @@ the chosen method. This method will emit signals such as ::Package(), then
 ::Finished() and then after a number of seconds ::Destroy() which will remove
 the interface from the bus.
 
-Attack vectors:
+There is a concern that a session service can be written to automatically
+authenticate methods, and replace the native client, but this is not possible.
+
+When authenticating, polkitd-1 passes a cookie to the authentication agent. If
+the user enters the right password, the authentication agent calls
+AuthenticationAgentResponse on the Authority with the cookie for the
+authentication request. If the caller of AuthenticationAgentResponse is not
+uid 0, then it is ignored.
+
+The authentication setuid root polkit-agent-helper-1 only decides to invoke this
+method if the user actually successfully authenticated. This of course relies on
+polkit-agent-helper-1 being a secure program. This is easy to verify since
+this is just over 300 lines of code and only depends on PAM (which is supposed
+to be secure) up until we have decided that the user successfully authenticated.
+Only when that is done, we initialize other libraries to send the message.
+
+Possible attack vectors:
 
  * A client could cause a local DoS (denial of service) by repeatedly calling
  GetTid without then calling a method to use this TID. This is mitigated by
@@ -68,11 +85,6 @@ Attack vectors:
  input to methods, and testing filenames for existence before they are passed
  to the backend.
 
- * A session service can be written to automatically authenticate methods, and
- replace the native client. This is hard to mitigate, as as soon as you have
- untrusted code running in the session, it's very easy to load exploit code
- using GTK_MODULES into previously trusted applications, such as gpk-application.
-
  * Issuing a large amount of data to a method to cause a local denial of
  service, for instance calling Resolve with millions of parameters. This is
  mitigated in the daemon by checking for a sane number of requests
commit c1ebffb5d58309f6cb935d464ac5fa78188263e1
Author: Richard Hughes <richard at hughsie.com>
Date:   Tue Jul 14 15:35:42 2009 +0100

    Raise the value of SimultaneousTransactionsForUid as pk-debuginfo-install uses more than 25 concurrent clients

diff --git a/etc/PackageKit.conf.in b/etc/PackageKit.conf.in
index daad73c..eb1de79 100644
--- a/etc/PackageKit.conf.in
+++ b/etc/PackageKit.conf.in
@@ -175,8 +175,8 @@ StateChangedTimeoutNormal=600
 # cause errors if the desktop client is doing many requests to the daemon in a
 # short period of time.
 #
-# default=25
-SimultaneousTransactionsForUid=25
+# default=100
+SimultaneousTransactionsForUid=100
 
 # The maximum number of items that can be resolved in one method
 #
commit 736a4d39c6268110beed46c193db5b5e1c6d3c00
Author: Richard Hughes <richard at hughsie.com>
Date:   Tue Jul 14 17:03:59 2009 +0100

    Add tweaks to the security file, after review from DavidZ

diff --git a/docs/security.txt b/docs/security.txt
index 5aff5a2..3f4bd4b 100644
--- a/docs/security.txt
+++ b/docs/security.txt
@@ -10,10 +10,10 @@ untrusted input from the client, which means the daemon is security sensitive.
 First, a high level overview, in this case using the yum backend as an example:
 
   gpk-update-icon      gpk-application
-        |              _______/
-     [DBUS] __[DBUS]__/
-        |  /
-   packagekitd -- [DBUS] -- polkit-daemon-1
+        |                 ___/
+     [D-Bus]  __[D-Bus]__/
+        |    /
+   packagekitd -- [D-Bus] -- polkitd-1
         |
       [pipe]
         |
@@ -21,17 +21,20 @@ First, a high level overview, in this case using the yum backend as an example:
 
 packagekitd does not expose itself remotely over XMLRPC or other remote
 interface, and so a remote denial of service or exploit is impossible without a
-serious exploit of other services such as DBus. It advertises a simple interface
+serious exploit of other services such as D-Bus. It advertises a simple interface
 that can be queried by clients in unprivileged and privileged modes.
 The privileged modes are controlled using PolicyKit, and policy and the
-authentication mechanism is deferred to the polkit-daemon-1 service.
+authentication mechanism is deferred to the polkitd-1 service.
 
-When a privileged method is executed, the daemon asks polkit-daemon-1 for
-authentication, which then blocks until the authentication is completed. This
-is handled asynchronously, and the daemon can process other requests whilst
-waiting for user input.
+When a privileged method is executed, the daemon checks with polkitd-1 daemon
+for whether the client is authorized for the action it wants to perform.
+This may involve the user authenticating that they are either the user (by
+typing their password) or that they are an administrative user (by typing the
+root password or the password of a user designated as an administrative user).
+The authorization check can take some time, but the daemon can process other
+requests whilst waiting for user input.
 
-The packagekitd daemon is started using DBus system activation, which means it
+The packagekitd daemon is started using D-Bus system activation, which means it
 is started without any environment (no PATH, etc) and therefore is impossible to
 exploit by preloading other libraries. It is also running as uid 0, and so
 requires root privileges to inject code into it.
commit dd4f22d95d01626c6ad72b8920673b1337706c3d
Author: Mounir Lamouri (volkmar) <mounir.lamouri at gmail.com>
Date:   Mon Jul 13 23:53:24 2009 +0200

    portage: get-requires basically rewrited but without non-installed packages management

diff --git a/backends/portage/portageBackend.py b/backends/portage/portageBackend.py
index b67265f..5aafa7a 100755
--- a/backends/portage/portageBackend.py
+++ b/backends/portage/portageBackend.py
@@ -739,29 +739,26 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
                             is_repository_enabled(installed_layman_db, o))
 
     def get_requires(self, filters, pkgs, recursive):
-        # TODO: filters
-        # TODO: recursive not implemented
-        # TODO: usefulness ? use cases
-        # TODO: special PDEPEND cases
+        # TODO: manage non-installed package
+
+        # FILTERS:
+        # - installed: error atm, see previous TODO
+        # - free: ok
+        # - newest: ignored because only one version of a package is installed
+
         self.status(STATUS_RUNNING)
         self.allow_cancel(True)
         self.percentage(None)
 
+        fltlist = filters.split(';')
+
         cpv_input = []
         cpv_list = []
 
-        myopts = {}
-        myopts["--selective"] = True
-        myopts["--deep"] = True
-        # TODO: keep remove ?
-        myparams = _emerge.create_depgraph_params.create_depgraph_params(
-                myopts, "remove")
-
-        settings, trees, _ = _emerge.actions.load_emerge_config()
-        rootconfig = _emerge.RootConfig.RootConfig(
-                self.portage_settings, trees["/"],
-                portage.sets.load_default_config(self.portage_settings,
-                    trees["/"]))
+        if FILTER_NOT_INSTALLED in fltlist:
+            self.error(ERROR_CANNOT_GET_REQUIRES,
+                    "get-requires returns only installed packages at the moment")
+            return
 
         for pkg in pkgs:
             cpv = id_to_cpv(pkg)
@@ -772,58 +769,64 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
                 continue
             if not self.is_installed(cpv):
                 self.error(ERROR_CANNOT_GET_REQUIRES,
-                        "get-requires is only available for installed packages")
+                        "get-requires is only available for installed packages at the moment")
                 continue
 
             cpv_input.append(cpv)
 
-            depgraph = _emerge.depgraph.depgraph(settings, trees, myopts,
-                    myparams, None)
-
-            required_set_names = ("system", "world")
-            required_sets = {}
-
-            for s in required_set_names:
-                required_sets[s] = portage.sets.base.InternalPackageSet(
-                        initial_atoms=rootconfig.setconfig.getSetAtoms(s))
-
-            # TODO: error/warning if world = null or system = null ?
-
-            # TODO: not sure it's needed. for deselect in emerge...
-            print required_sets["world"]
-
-            for cpv in self.vardb.cpv_all():
-                if cpv not in cpv_input:
-                    required_sets["world"].add("=" + cpv)
-
-            set_args = {}
-            for s, pkg_set in required_sets.iteritems():
-                #print s
-                #print pkg_set
-                set_atom = portage.sets.SETPREFIX + s
-                set_arg = _emerge.SetArg.SetArg(arg=set_atom, set=pkg_set,
-                        root_config=depgraph._frozen_config.roots[portage.settings["ROOT"]])
-                set_args[s] = set_arg
-                for atom in set_arg.set:
-                    print atom
-                    depgraph._dynamic_config._dep_stack.append(
-                            _emerge.Dependency.Dependency(atom=atom, root=portage.settings["ROOT"],
-                                parent=set_arg))
-                depgraph._dynamic_config.digraph.add(set_arg, None)
-
-            if not depgraph._complete_graph():
-                self.error(ERROR_INTERNAL_ERROR, "Error when generating depgraph")
-                continue
+        myopts = {}
+        myopts["--selective"] = True
+        myopts["--deep"] = True
+        # TODO: keep remove ?
+        settings, trees, _ = _emerge.actions.load_emerge_config()
+        myparams = _emerge.create_depgraph_params.create_depgraph_params(
+                myopts, "remove")
 
-            for node in depgraph._dynamic_config.digraph.__iter__():
-                if isinstance(node, _emerge.SetArg.SetArg):
-                    continue
-                if node.cpv in cpv_input:
+        depgraph = _emerge.depgraph.depgraph(settings, trees, myopts,
+                myparams, None)
+
+        # TODO: atm, using FILTER_INSTALLED because it's quicker
+        # and we don't want to manage non-installed packages
+        for cp in self.get_all_cp([FILTER_INSTALLED]):
+            for cpv in self.get_all_cpv(cpv, [FILTER_INSTALLED]):
+                depgraph._dynamic_config._dep_stack.append(
+                        _emerge.Dependency.Dependency(atom=portage.dep.Atom('=' + cpv),
+                            root=portage.settings["ROOT"], parent=None))
+
+        if not depgraph._complete_graph():
+            self.error(ERROR_INTERNAL_ERROR, "Error when generating depgraph")
+            return
+
+        def _add_children_to_list(cpv_list, node):
+            for n in depgraph._dynamic_config.digraph.parent_nodes(node):
+                if n not in cpv_list and not isinstance(n, _emerge.SetArg.SetArg):
+                    cpv_list.append(n)
+                    _add_children_to_list(cpv_list, n)
+
+        for node in depgraph._dynamic_config.digraph.__iter__():
+            if isinstance(node, _emerge.SetArg.SetArg):
+                continue
+            if node.cpv in cpv_input:
+                if recursive:
+                    _add_children_to_list(cpv_list, node)
+                else:
                     for n in depgraph._dynamic_config.digraph.parent_nodes(node):
-                        self.package(n[2])
-                        #for m in depgraph._dynamic_config.digraph.parent_nodes(n):
-                        #    print m
-            #depgraph._dynamic_config.digraph.debug_print()
+                        cpv_list.append(n)
+
+        # now we can change cpv_list to a real cpv list
+        tmp_list = cpv_list[:]
+        cpv_list = []
+        for x in tmp_list:
+            cpv_list.append(x[2])
+        del tmp_list
+
+        # free filter
+        cpv_list = self.filter_free(cpv_list, fltlist)
+
+        for cpv in cpv_list:
+            # prevent showing input packages
+            if '=' + cpv not in cpv_input:
+                self.package(cpv)
 
     def get_update_detail(self, pkgs):
         # TODO: a lot of informations are missing
commit a81e8b0c43abf362da027b05447744edc85d95a7
Author: Mounir Lamouri (volkmar) <mounir.lamouri at gmail.com>
Date:   Mon Jul 13 18:23:41 2009 +0200

    portage: get-depends using a patch preventing portage's output, get-requires cleans up

diff --git a/backends/portage/portageBackend.py b/backends/portage/portageBackend.py
index 51c0fe1..b67265f 100755
--- a/backends/portage/portageBackend.py
+++ b/backends/portage/portageBackend.py
@@ -528,7 +528,6 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
     def get_depends(self, filters, pkgs, recursive):
         # TODO: use only myparams ?
         # TODO: improve error management / info
-        # TODO: show DEPEND-only depends too
 
         # FILTERS:
         # - installed: ok
@@ -555,18 +554,13 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
         myopts = {}
         myopts["--selective"] = True
         myopts["--deep"] = True
-        spinner = _emerge.stdout_spinner.stdout_spinner()
         settings, trees, _ = _emerge.actions.load_emerge_config()
         myparams = _emerge.create_depgraph_params.create_depgraph_params(
                 myopts, "")
 
-        try:
-            self.block_output()
-            depgraph = _emerge.depgraph.depgraph(
-                    settings, trees, myopts, myparams, spinner)
-            retval, fav = depgraph.select_files(cpv_input)
-        finally:
-            self.unblock_output()
+        depgraph = _emerge.depgraph.depgraph(
+                settings, trees, myopts, myparams, None)
+        retval, fav = depgraph.select_files(cpv_input)
 
         if not retval:
             self.error(ERROR_INTERNAL_ERROR,
@@ -581,6 +575,8 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
 
         for cpv in cpv_input:
             for r in depgraph._dynamic_config.digraph.root_nodes():
+                # TODO: remove things with @ as first char
+                # TODO: or refuse SetArgs
                 if not isinstance(r, _emerge.AtomArg.AtomArg):
                     continue
                 if r.atom == cpv:
@@ -746,15 +742,21 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
         # TODO: filters
         # TODO: recursive not implemented
         # TODO: usefulness ? use cases
+        # TODO: special PDEPEND cases
         self.status(STATUS_RUNNING)
         self.allow_cancel(True)
         self.percentage(None)
 
+        cpv_input = []
+        cpv_list = []
+
         myopts = {}
-        spinner = ""
-        favorites = []
+        myopts["--selective"] = True
+        myopts["--deep"] = True
+        # TODO: keep remove ?
+        myparams = _emerge.create_depgraph_params.create_depgraph_params(
+                myopts, "remove")
 
-        spinner = _emerge.stdout_spinner.stdout_spinner()
         settings, trees, _ = _emerge.actions.load_emerge_config()
         rootconfig = _emerge.RootConfig.RootConfig(
                 self.portage_settings, trees["/"],
@@ -773,22 +775,13 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
                         "get-requires is only available for installed packages")
                 continue
 
-            required_set_names = ("system", "world")
-            required_sets = {}
-
-            args_set = portage.sets.base.InternalPackageSet()
-            args_set.update(["="+cpv]) # parameters is converted to atom
-            # or use portage.dep_expand
+            cpv_input.append(cpv)
 
-            if not args_set:
-                self.error(ERROR_INTERNAL_ERROR,
-                        "Was not able to generate atoms")
-                continue
-            
             depgraph = _emerge.depgraph.depgraph(settings, trees, myopts,
-                    _emerge.create_depgraph_params.create_depgraph_params(
-                        myopts, "remove"), spinner)
-            vardb = depgraph._frozen_config.trees["/"]["vartree"].dbapi
+                    myparams, None)
+
+            required_set_names = ("system", "world")
+            required_sets = {}
 
             for s in required_set_names:
                 required_sets[s] = portage.sets.base.InternalPackageSet(
@@ -797,52 +790,40 @@ class PackageKitPortageBackend(PackageKitBaseBackend, PackagekitPackage):
             # TODO: error/warning if world = null or system = null ?
 
             # TODO: not sure it's needed. for deselect in emerge...
-            required_sets["world"].clear()
-            for pkg in vardb:
-                try:
-                    if args_set.findAtomForPackage(pkg) is None:
-                        required_sets["world"].add("=" + pkg.cpv)
-                except portage.exception.InvalidDependString, e:
-                    required_sets["world"].add("=" + pkg.cpv)
+            print required_sets["world"]
+
+            for cpv in self.vardb.cpv_all():
+                if cpv not in cpv_input:
+                    required_sets["world"].add("=" + cpv)
 
             set_args = {}
             for s, pkg_set in required_sets.iteritems():
+                #print s
+                #print pkg_set
                 set_atom = portage.sets.SETPREFIX + s
                 set_arg = _emerge.SetArg.SetArg(arg=set_atom, set=pkg_set,
                         root_config=depgraph._frozen_config.roots[portage.settings["ROOT"]])
                 set_args[s] = set_arg
                 for atom in set_arg.set:
+                    print atom
                     depgraph._dynamic_config._dep_stack.append(
                             _emerge.Dependency.Dependency(atom=atom, root=portage.settings["ROOT"],
                                 parent=set_arg))
-                    depgraph._dynamic_config.digraph.add(set_arg, None)
+                depgraph._dynamic_config.digraph.add(set_arg, None)
 
             if not depgraph._complete_graph():
                 self.error(ERROR_INTERNAL_ERROR, "Error when generating depgraph")
                 continue
 
-            def cmp_pkg_cpv(pkg1, pkg2):
-                if pkg1.cpv > pkg2.cpv:
-                    return 1
-                elif pkg1.cpv == pkg2.cpv:
-                    return 0
-                else:
-                    return -1
-
-            for pkg in sorted(vardb,
-                    key=portage.util.cmp_sort_key(cmp_pkg_cpv)):
-                arg_atom = None
-                try:
-                    arg_atom = args_set.findAtomForPackage(pkg)
-                except portage.exception.InvalidDependString:
+            for node in depgraph._dynamic_config.digraph.__iter__():
+                if isinstance(node, _emerge.SetArg.SetArg):
                     continue
-
-                if arg_atom and pkg in depgraph._dynamic_config.digraph:
-                    parents = depgraph._dynamic_config.digraph.parent_nodes(pkg)
-                    for node in parents:
-                        self.package(node[2])
-
-        self.percentage(100)
+                if node.cpv in cpv_input:
+                    for n in depgraph._dynamic_config.digraph.parent_nodes(node):
+                        self.package(n[2])
+                        #for m in depgraph._dynamic_config.digraph.parent_nodes(n):
+                        #    print m
+            #depgraph._dynamic_config.digraph.debug_print()
 
     def get_update_detail(self, pkgs):
         # TODO: a lot of informations are missing
commit b694bcc8dc0972da9e5f3283774ff987d718568d
Author: Richard Hughes <richard at hughsie.com>
Date:   Mon Jul 13 11:42:45 2009 +0100

    Move two compile time defines to the config file

diff --git a/etc/PackageKit.conf.in b/etc/PackageKit.conf.in
index 702a6df..daad73c 100644
--- a/etc/PackageKit.conf.in
+++ b/etc/PackageKit.conf.in
@@ -195,3 +195,22 @@ MaximumItemsToResolve=10
 # default=2500
 MaximumPackagesToProcess=2500
 
+# How long the transaction is valid before it's destroyed, in seconds
+#
+# The client only has a finite amount of time to use the object, else it is
+# destroyed. Setting this longer will allow malicious clients to queue up large
+# number of authentication requests, but setting this shorter will reduce the
+# amount of time the user has to authenticate.
+#
+# default=30
+TransactionCreateCommitTimeout=30
+
+# How long the transaction should be queriable after it is finished, in seconds
+#
+# Give the client a few seconds to still query the transaction after it has
+# finished by keeping it on the bus. Setting this larger allows clients to query
+# the transaction without accessing the database, but increases memory usage.
+#
+# default=5
+TransactionKeepFinishedTimeout=5
+
diff --git a/src/pk-transaction-list.c b/src/pk-transaction-list.c
index 03234ea..7aee7c8 100644
--- a/src/pk-transaction-list.c
+++ b/src/pk-transaction-list.c
@@ -50,12 +50,6 @@ static void     pk_transaction_list_finalize	(GObject        *object);
 
 #define PK_TRANSACTION_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PK_TYPE_TRANSACTION_LIST, PkTransactionListPrivate))
 
-/* how long the transaction should be queriable after it is finished, in seconds */
-#define PK_TRANSACTION_LIST_KEEP_FINISHED_TIMEOUT	5
-
-/* how long the tid is valid before it's destroyed, in seconds */
-#define PK_TRANSACTION_LIST_CREATE_COMMIT_TIMEOUT	30
-
 /* the interval between each CST, in seconds */
 #define PK_TRANSACTION_WEDGE_CHECK			10
 
@@ -287,6 +281,7 @@ pk_transaction_list_transaction_finished_cb (PkTransaction *transaction, const g
 {
 	guint i;
 	guint length;
+	guint timeout;
 	PkTransactionItem *item;
 	const gchar *tid;
 
@@ -321,8 +316,8 @@ pk_transaction_list_transaction_finished_cb (PkTransaction *transaction, const g
 	g_signal_emit (tlist, signals [PK_TRANSACTION_LIST_CHANGED], 0);
 
 	/* give the client a few seconds to still query the runner */
-	item->remove_id = g_timeout_add_seconds (PK_TRANSACTION_LIST_KEEP_FINISHED_TIMEOUT,
-						 (GSourceFunc) pk_transaction_list_remove_item_cb, item);
+	timeout = pk_conf_get_int (tlist->priv->conf, "TransactionKeepFinishedTimeout");
+	item->remove_id = g_timeout_add_seconds (timeout, (GSourceFunc) pk_transaction_list_remove_item_cb, item);
 
 	/* do the next transaction now if we have another queued */
 	length = tlist->priv->array->len;
@@ -344,8 +339,7 @@ pk_transaction_list_transaction_finished_cb (PkTransaction *transaction, const g
 static gboolean
 pk_transaction_list_no_commit_cb (PkTransactionItem *item)
 {
-	egg_warning ("ID %s was not committed in %i seconds!",
-		     item->tid, PK_TRANSACTION_LIST_CREATE_COMMIT_TIMEOUT);
+	egg_warning ("ID %s was not committed in time!", item->tid);
 	pk_transaction_list_remove_internal (item->list, item);
 
 	/* never repeat */
@@ -383,6 +377,7 @@ pk_transaction_list_create (PkTransactionList *tlist, const gchar *tid, const gc
 {
 	guint count;
 	guint max_count;
+	guint timeout;
 	gboolean ret = FALSE;
 	PkTransactionItem *item;
 	DBusGConnection *connection;
@@ -466,8 +461,8 @@ pk_transaction_list_create (PkTransactionList *tlist, const gchar *tid, const gc
 	dbus_g_connection_register_g_object (connection, item->tid, G_OBJECT (item->transaction));
 
 	/* the client only has a finite amount of time to use the object, else it's destroyed */
-	item->commit_id = g_timeout_add_seconds (PK_TRANSACTION_LIST_CREATE_COMMIT_TIMEOUT,
-					      (GSourceFunc) pk_transaction_list_no_commit_cb, item);
+	timeout = pk_conf_get_int (tlist->priv->conf, "TransactionCreateCommitTimeout");
+	item->commit_id = g_timeout_add_seconds (timeout, (GSourceFunc) pk_transaction_list_no_commit_cb, item);
 
 	egg_debug ("adding transaction %p, item %p", item->transaction, item);
 	g_ptr_array_add (tlist->priv->array, item);


More information about the PackageKit-commit mailing list