/*
* Copyright (C) 2007  Koos Vriezen <koos.vriezen@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/* gcc -o knpplayer `pkg-config --libs --cflags gtk+-x11-2.0` `pkg-config --libs --cflags dbus-glib-1` `pkg-config --libs gthread-2.0` npplayer.c

http://devedge-temp.mozilla.org/library/manuals/2002/plugin/1.0/
http://dbus.freedesktop.org/doc/dbus/libdbus-tutorial.html
*/

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>

#include <glib.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>

#define XP_UNIX
#define MOZ_X11
#include "moz-sdk/npupp.h"

typedef const char* (* NP_LOADDS NP_GetMIMEDescriptionUPP)();
typedef NPError (* NP_GetValueUPP)(void *inst, NPPVariable var, void *value);
typedef NPError (* NP_InitializeUPP)(NPNetscapeFuncs*, NPPluginFuncs*);
typedef NPError (* NP_ShutdownUPP)(void);

static gchar *plugin;
static gchar *object_url;
static gchar *mimetype;

static DBusConnection *dbus_connection;
static char *service_name;
static gchar *callback_service;
static gchar *callback_path;
static GModule *library;
static GtkWidget *xembed;
static Window socket_id;
static Window parent_id;
static int top_w, top_h;
static int update_dimension_timer;
static int stdin_read_watch;

static NPPluginFuncs np_funcs;       /* plugin functions              */
static NPP npp;                      /* single instance of the plugin */
static NPWindow np_window;
static NPObject *js_window;
static NPObject *scriptable_peer;
static NPSavedData *saved_data;
static NPClass js_class;
static GTree *stream_list;
static gpointer current_stream_id;
static uint32_t stream_chunk_size;
static char stream_buf[32 * 1024];
static unsigned int stream_buf_pos;
static int stream_id_counter;
static GTree *identifiers;
static int js_obj_counter;
typedef struct _StreamInfo {
    NPStream np_stream;
    /*unsigned int stream_buf_pos;*/
    unsigned int stream_pos;
    unsigned int total;
    unsigned int reason;
    char *url;
    char *mimetype;
    char *target;
    bool notify;
    bool called_plugin;
    bool destroyed;
} StreamInfo;
struct JsObject;
typedef struct _JsObject {
    NPObject npobject;
    struct _JsObject * parent;
    char * name;
} JsObject;

static NP_GetMIMEDescriptionUPP npGetMIMEDescription;
static NP_GetValueUPP npGetValue;
static NP_InitializeUPP npInitialize;
static NP_ShutdownUPP npShutdown;

static void callFunction(int stream, const char *func, int first_arg_type, ...);
static void readStdin (gpointer d, gint src, GdkInputCondition cond);
static char * evaluate (const char *script);

/*----------------%<---------------------------------------------------------*/

static void print (const char * format, ...) {
    va_list vl;
    va_start (vl, format);
    vprintf (format, vl);
    va_end (vl);
    fflush (stdout);
}

/*----------------%<---------------------------------------------------------*/

static gint streamCompare (gconstpointer a, gconstpointer b) {
    return (long)a - (long)b;
}

static void freeStream (StreamInfo *si) {
    if (!g_tree_remove (stream_list, si->np_stream.ndata))
        print ("WARNING freeStream not in tree\n");
    g_free (si->url);
    if (si->mimetype)
        g_free (si->mimetype);
    if (si->target)
        g_free (si->target);
    free (si);
}

static gboolean requestStream (void * p) {
    StreamInfo *si = (StreamInfo *) g_tree_lookup (stream_list, p);
    if (si) {
        if (!callback_service)
            current_stream_id = p;
        if (!stdin_read_watch)
            stdin_read_watch = gdk_input_add (0, GDK_INPUT_READ,readStdin,NULL);
        if (si->target)
            callFunction ((int)(long)p, "getUrl",
                    DBUS_TYPE_STRING, &si->url,
                    DBUS_TYPE_STRING, &si->target, DBUS_TYPE_INVALID);
        else
            callFunction ((int)(long)p, "getUrl",
                    DBUS_TYPE_STRING, &si->url, DBUS_TYPE_INVALID);
    } else {
        print ("requestStream %d not found", (long) p);
    }
    return 0; /* single shot */
}

static gboolean destroyStream (void * p) {
    StreamInfo *si = (StreamInfo *) g_tree_lookup (stream_list, p);
    print ("FIXME destroyStream\n");
    if (si)
        callFunction ((int)(long)p, "destroy", DBUS_TYPE_INVALID);
    return 0; /* single shot */
}

static void removeStream (void * p) {
    StreamInfo *si = (StreamInfo *) g_tree_lookup (stream_list, p);

    if (si) {
        print ("removeStream %d rec:%d reason %d\n", (long) p, si->stream_pos, si->reason);
        if (!si->destroyed) {
            if (si->called_plugin && !si->target) {
                si->np_stream.end = si->total;
                np_funcs.destroystream (npp, &si->np_stream, si->reason);
            }
            if (si->notify)
                np_funcs.urlnotify (npp,
                        si->url, si->reason, si->np_stream.notifyData);
        }
        freeStream (si);
    }
}

static int32_t writeStream (gpointer p, char *buf, uint32_t count) {
    int32_t sz = -1;
    StreamInfo *si = (StreamInfo *) g_tree_lookup (stream_list, p);
    /*print ("writeStream found %d count %d\n", !!si, count);*/
    if (si) {
        if (si->reason > NPERR_NO_ERROR) {
            sz = count; /* stream closed, skip remainings */
        } else {
            if (!si->called_plugin) {
                uint16 stype = NP_NORMAL;
                NPError err = np_funcs.newstream (npp, si->mimetype
                        ?  si->mimetype
                        : "text/plain",
                        &si->np_stream, 0, &stype);
                if (err != NPERR_NO_ERROR) {
                    g_printerr ("newstream error %d\n", err);
                    destroyStream (p);
                    return count; /* stream not accepted, skip remainings */
                }
                print ("newStream %d type:%d\n", (long) p, stype);
                si->called_plugin = true;
            }
            if (count) /* urls with a target returns zero bytes */
                sz = np_funcs.writeready (npp, &si->np_stream);
            if (sz > 0) {
                sz = np_funcs.write (npp, &si->np_stream, si->stream_pos,
                        (int32_t) count > sz ? sz : (int32_t) count, buf);
                if (sz < 0) /*FIXME plugin destroys stream here*/
                    g_timeout_add (0, destroyStream, p);
            } else {
                sz = 0;
            }
            si->stream_pos += sz;
            if (si->stream_pos == si->total) {
                if (si->stream_pos || !count)
                    removeStream (p);
                else
                    g_timeout_add (0, destroyStream, p);
            }
        }
    }
    return sz;
}

static StreamInfo *addStream (const char *url, const char *mime, const char *target, void *notify_data, bool notify) {
    StreamInfo *si = (StreamInfo *) malloc (sizeof (StreamInfo));

    memset (si, 0, sizeof (StreamInfo));
    si->url = g_strdup (url);
    si->np_stream.url = si->url;
    if (mime)
        si->mimetype = g_strdup (mime);
    if (target)
        si->target = g_strdup (target);
    si->np_stream.notifyData = notify_data;
    si->notify = notify;
    si->np_stream.ndata = (void *) (long) (stream_id_counter++);
    print ("add stream %d\n", (long) si->np_stream.ndata);
    g_tree_insert (stream_list, si->np_stream.ndata, si);

    g_timeout_add (0, requestStream, si->np_stream.ndata);

    return si;
}

/*----------------%<---------------------------------------------------------*/

static void createJsName (JsObject * obj, char **name, uint32_t * len) {
    int slen = strlen (obj->name);
    if (obj->parent) {
        *len += slen + 1;
        createJsName (obj->parent, name, len);
    } else {
        *name = (char *) malloc (*len + slen + 1);
        *(*name + *len + slen) = 0;
        *len = 0;
    }
    if (obj->parent) {
        *(*name + *len) = '.';
        *len += 1;
    }
    memcpy (*name + *len, obj->name, slen);
    *len += slen;
}

static char *nsVariant2Str (const NPVariant *value) {
    char *str;
    switch (value->type) {
        case NPVariantType_String:
            str = (char *) malloc (value->value.stringValue.utf8length + 3);
            sprintf (str, "'%s'", value->value.stringValue.utf8characters);
            break;
        case NPVariantType_Int32:
            str = (char *) malloc (16);
            snprintf (str, 15, "%d", value->value.intValue);
            break;
        case NPVariantType_Double:
            str = (char *) malloc (64);
            snprintf (str, 63, "%f", value->value.doubleValue);
            break;
        case NPVariantType_Bool:
            str = strdup (value->value.boolValue ? "true" : "false");
            break;
        case NPVariantType_Null:
            str = strdup ("null");
            break;
        case NPVariantType_Object:
            if (&js_class == value->value.objectValue->_class) {
                JsObject *jv = (JsObject *) value->value.objectValue;
                char *val;
                uint32_t vlen = 0;
                createJsName (jv, &val, &vlen);
                str = strdup (val);
                free (val);
            }
            break;
        default:
            str = strdup ("");
            break;
    }
    return str;
}

/*----------------%<---------------------------------------------------------*/

static NPObject * nsCreateObject (NPP instance, NPClass *aClass) {
    NPObject *obj;
    if (aClass && aClass->allocate) {
        obj = aClass->allocate (instance, aClass);
    } else {
        obj = (NPObject *) malloc (sizeof (NPObject));
        memset (obj, 0, sizeof (NPObject));
        obj->_class = aClass;
        /*obj = js_class.allocate (instance, &js_class);/ *add null class*/
        print ("NPN_CreateObject\n");
    }
    obj->referenceCount = 1;
    return obj;
}

static NPObject *nsRetainObject (NPObject *npobj) {
    /*print( "nsRetainObject %p\n", npobj);*/
    npobj->referenceCount++;
    return npobj;
}

static void nsReleaseObject (NPObject *obj) {
    /*print ("NPN_ReleaseObject\n");*/
    if (! (--obj->referenceCount))
        obj->_class->deallocate (obj);
}

static NPError nsGetURL (NPP instance, const char* url, const char* target) {
    (void)instance;
    print ("nsGetURL %s %s\n", url, target ? target : "");
    addStream (url, 0L, target, 0L, false);
    return NPERR_NO_ERROR;
}

static NPError nsPostURL (NPP instance, const char *url,
        const char *target, uint32 len, const char *buf, NPBool file) {
    (void)instance; (void)len; (void)buf; (void)file;
    print ("nsPostURL %s %s\n", url, target ? target : "");
    addStream (url, 0L, target, 0L, false);
    return NPERR_NO_ERROR;
}

static NPError nsRequestRead (NPStream *stream, NPByteRange *rangeList) {
    (void)stream; (void)rangeList;
    print ("nsRequestRead\n");
    return NPERR_NO_ERROR;
}

static NPError nsNewStream (NPP instance, NPMIMEType type,
        const char *target, NPStream **stream) {
    (void)instance; (void)type; (void)stream; (void)target;
    print ("nsNewStream\n");
    return NPERR_NO_ERROR;
}

static int32 nsWrite (NPP instance, NPStream* stream, int32 len, void *buf) {
    (void)instance; (void)len; (void)buf; (void)stream;
    print ("nsWrite\n");
    return 0;
}

static NPError nsDestroyStream (NPP instance, NPStream *stream, NPError reason) {
    StreamInfo *si = (StreamInfo *) g_tree_lookup (stream_list, stream->ndata);
    (void)instance;
    print ("nsDestroyStream\n");
    if (si) {
        si->reason = reason;
        si->destroyed = true;
        g_timeout_add (0, destroyStream, stream->ndata);
        return NPERR_NO_ERROR;
    }
    return NPERR_NO_DATA;
}

static void nsStatus (NPP instance, const char* message) {
    (void)instance;
    print ("NPN_Status %s\n", message ? message : "-");
}

static const char* nsUserAgent (NPP instance) {
    (void)instance;
    print ("NPN_UserAgent\n");
    return "";
}

static void *nsAlloc (uint32 size) {
    return malloc (size);
}

static void nsMemFree (void* ptr) {
    free (ptr);
}

static uint32 nsMemFlush (uint32 size) {
    (void)size;
    print ("NPN_MemFlush\n");
    return 0;
}

static void nsReloadPlugins (NPBool reloadPages) {
    (void)reloadPages;
    print ("NPN_ReloadPlugins\n");
}

static JRIEnv* nsGetJavaEnv () {
    print ("NPN_GetJavaEnv\n");
    return NULL;
}

static jref nsGetJavaPeer (NPP instance) {
    (void)instance;
    print ("NPN_GetJavaPeer\n");
    return NULL;
}

static NPError nsGetURLNotify (NPP instance, const char* url, const char* target, void *notify) {
    (void)instance;
    print ("NPN_GetURLNotify %s %s\n", url, target ? target : "");
    addStream (url, 0L, target, notify, true);
    return NPERR_NO_ERROR;
}

static NPError nsPostURLNotify (NPP instance, const char* url, const char* target, uint32 len, const char* buf, NPBool file, void *notify) {
    (void)instance; (void)len; (void)buf; (void)file;
    print ("NPN_PostURLNotify\n");
    addStream (url, 0L, target, notify, true);
    return NPERR_NO_ERROR;
}

static NPError nsGetValue (NPP instance, NPNVariable variable, void *value) {
    print ("NPN_GetValue %d\n", variable & ~NP_ABI_MASK);
    switch (variable) {
        case NPNVxDisplay:
            *(void**)value = (void*)(long) gdk_x11_get_default_xdisplay ();
            break;
        case NPNVxtAppContext:
            *(void**)value = NULL;
            break;
        case NPNVnetscapeWindow:
            print ("NPNVnetscapeWindow\n");
            break;
        case NPNVjavascriptEnabledBool:
            *(int*)value = 1;
            break;
        case NPNVasdEnabledBool:
            *(int*)value = 0;
            break;
        case NPNVisOfflineBool:
            *(int*)value = 0;
            break;
        case NPNVserviceManager:
            *(int*)value = 0;
            break;
        case NPNVToolkit:
            *(int*)value = NPNVGtk2;
            break;
        case NPNVSupportsXEmbedBool:
            *(int*)value = 1;
            break;
        case NPNVWindowNPObject:
            if (!js_window) {
                JsObject *jo = (JsObject*) nsCreateObject (instance, &js_class);
                jo->name = g_strdup ("window");
                js_window = (NPObject *) jo;
            }
            *(NPObject**)value = nsRetainObject (js_window);
            break;
        case NPNVPluginElementNPObject: {
            JsObject * obj = (JsObject *) nsCreateObject (instance, &js_class);
            obj->name = g_strdup ("this");
            *(NPObject**)value = (NPObject *) obj;
            break;
        }
        default:
            *(int*)value = 0;
            print ("unknown value\n");
            return NPERR_GENERIC_ERROR;
    }
    return NPERR_NO_ERROR;
}

static NPError nsSetValue (NPP instance, NPPVariable variable, void *value) {
    /* NPPVpluginWindowBool */
    (void)instance; (void)value;
    print ("NPN_SetValue %d\n", variable & ~NP_ABI_MASK);
    return NPERR_NO_ERROR;
}

static void nsInvalidateRect (NPP instance, NPRect *invalidRect) {
    (void)instance; (void)invalidRect;
    print ("NPN_InvalidateRect\n");
}

static void nsInvalidateRegion (NPP instance, NPRegion invalidRegion) {
    (void)instance; (void)invalidRegion;
    print ("NPN_InvalidateRegion\n");
}

static void nsForceRedraw (NPP instance) {
    (void)instance;
    print ("NPN_ForceRedraw\n");
}

static NPIdentifier nsGetStringIdentifier (const NPUTF8* name) {
    /*print ("NPN_GetStringIdentifier %s\n", name);*/
    gpointer id = g_tree_lookup (identifiers, name);
    if (!id) {
        id = strdup (name);
        g_tree_insert (identifiers, id, id);
    }
    return id;
}

static void nsGetStringIdentifiers (const NPUTF8** names, int32_t nameCount,
        NPIdentifier* ids) {
    (void)names; (void)nameCount; (void)ids;
    print ("NPN_GetStringIdentifiers\n");
}

static NPIdentifier nsGetIntIdentifier (int32_t intid) {
    print ("NPN_GetIntIdentifier %d\n", intid);
    return (NPIdentifier) (long) intid;
}

static bool nsIdentifierIsString (NPIdentifier name) {
    print ("NPN_IdentifierIsString\n");
    return !!g_tree_lookup (identifiers, name);
}

static NPUTF8 * nsUTF8FromIdentifier (NPIdentifier name) {
    print ("NPN_UTF8FromIdentifier\n");
    char *str = g_tree_lookup (identifiers, name);
    if (str)
        return strdup (str);
    return NULL;
}

static int32_t nsIntFromIdentifier (NPIdentifier identifier) {
    print ("NPN_IntFromIdentifier\n");
    return (int32_t) (long) identifier;
}

static bool nsInvoke (NPP instance, NPObject * npobj, NPIdentifier method,
        const NPVariant *args, uint32_t arg_count, NPVariant *result) {
    (void)instance;
    /*print ("NPN_Invoke %s\n", id);*/
    return npobj->_class->invoke (npobj, method, args, arg_count, result);
}

static bool nsInvokeDefault (NPP instance, NPObject * npobj,
        const NPVariant * args, uint32_t arg_count, NPVariant * result) {
    (void)instance;
    return npobj->_class->invokeDefault (npobj,args, arg_count, result);
}

static bool nsEvaluate (NPP instance, NPObject * npobj, NPString * script,
        NPVariant * result) {
    char * this_var;
    char * this_var_type;
    char * this_var_string;
    char * jsscript;
    (void) npobj; /*FIXME scope, search npobj window*/
    print ("NPN_Evaluate:");

    /* assign to a js variable */
    this_var = (char *) malloc (64);
    sprintf (this_var, "this.__kmplayer__obj_%d", js_obj_counter);

    jsscript = (char *) malloc (strlen (this_var) + script->utf8length + 3);
    sprintf (jsscript, "%s=%s;", this_var, script->utf8characters);
    this_var_string = evaluate (jsscript);
    free (jsscript);

    if (this_var_string) {
        /* get type of js this_var */
        jsscript = (char *) malloc (strlen (this_var) + 9);
        sprintf (jsscript, "typeof %s;", this_var);
        this_var_type = evaluate (jsscript);
        free (jsscript);

        if (this_var_type) {
            if (!strcasecmp (this_var_type, "undefined")) {
                result->type = NPVariantType_Null;
            } else if (!strcasecmp (this_var_type, "object")) {
                JsObject *jo = (JsObject *)nsCreateObject (instance, &js_class);
                js_obj_counter++;
                result->type = NPVariantType_Object;
                jo->name = g_strdup (this_var);
                result->value.objectValue = (NPObject *)jo;
            } else { /* FIXME numbers/void/undefined*/
                result->type = NPVariantType_String;
                result->value.stringValue.utf8characters =
                    g_strdup (this_var_string);
                result->value.stringValue.utf8length=strlen (this_var_string)+1;
            }
            g_free (this_var_type);
        }
        g_free (this_var_string);
    } else {
        print ("   => error\n");
        return false;
    }
    free (this_var);

    return true;
}

static bool nsGetProperty (NPP instance, NPObject * npobj,
        NPIdentifier property, NPVariant * result) {
    (void)instance;
    return npobj->_class->getProperty (npobj, property, result);
}

static bool nsSetProperty (NPP instance, NPObject * npobj,
        NPIdentifier property, const NPVariant *value) {
    (void)instance;
    return npobj->_class->setProperty (npobj, property, value);
}

static bool nsRemoveProperty (NPP inst, NPObject * npobj, NPIdentifier prop) {
    (void)inst;
    return npobj->_class->removeProperty (npobj, prop);
}

static bool nsHasProperty (NPP instance, NPObject * npobj, NPIdentifier prop) {
    (void)instance;
    return npobj->_class->hasProperty (npobj, prop);
}

static bool nsHasMethod (NPP instance, NPObject * npobj, NPIdentifier method) {
    (void)instance;
    return npobj->_class->hasMethod (npobj, method);
}

static void nsReleaseVariantValue (NPVariant * variant) {
    /*print ("NPN_ReleaseVariantValue\n");*/
    switch (variant->type) {
        case NPVariantType_String:
            if (variant->value.stringValue.utf8characters)
                g_free ((char *) variant->value.stringValue.utf8characters);
            break;
        case NPVariantType_Object:
            if (variant->value.objectValue)
                nsReleaseObject (variant->value.objectValue);
            break;
        default:
            break;
    }
    variant->type = NPVariantType_Null;
}

static void nsSetException (NPObject *npobj, const NPUTF8 *message) {
    (void)npobj;
    print ("NPN_SetException %s\n", message ? message : "-");
}

static bool nsPushPopupsEnabledState (NPP instance, NPBool enabled) {
    (void)instance;
    print ("NPN_PushPopupsEnabledState %d\n", enabled);
    return false;
}

static bool nsPopPopupsEnabledState (NPP instance) {
    (void)instance;
    print ("NPN_PopPopupsEnabledState\n");
    return false;
}

/*----------------%<---------------------------------------------------------*/

static NPObject * windowClassAllocate (NPP instance, NPClass *aClass) {
    (void)instance;
    /*print ("windowClassAllocate\n");*/
    JsObject * jo = (JsObject *) malloc (sizeof (JsObject));
    memset (jo, 0, sizeof (JsObject));
    jo->npobject._class = aClass;
    return (NPObject *) jo;
}

static void windowClassDeallocate (NPObject *npobj) {
    JsObject *jo = (JsObject *) npobj;
    /*print ("windowClassDeallocate\n");*/
    if (jo->parent) {
        nsReleaseObject ((NPObject *) jo->parent);
    } else if (jo->name && !strncmp (jo->name, "this.__kmplayer__obj_", 21)) {
        char *script = (char *) malloc (strlen (jo->name) + 7);
        char *result;
        char *counter = strrchr (jo->name, '_');
        sprintf (script, "%s=null;", jo->name);
        result = evaluate (script);
        free (script);
        g_free (result);
        if (counter) {
            int c = strtol (counter +1, NULL, 10);
            if (c == js_obj_counter -1)
                js_obj_counter--; /*poor man's variable name reuse */
        }
    }
    if (jo->name)
        g_free (jo->name);
    if (npobj == js_window) {
        print ("WARNING deleting window object\n");
        js_window = NULL;
    }
    free (npobj);
}

static void windowClassInvalidate (NPObject *npobj) {
    (void)npobj;
    print ("windowClassInvalidate\n");
}

static bool windowClassHasMethod (NPObject *npobj, NPIdentifier name) {
    (void)npobj; (void)name;
    print ("windowClassHasMehtod\n");
    return false;
}

static bool windowClassInvoke (NPObject *npobj, NPIdentifier method,
        const NPVariant *args, uint32_t arg_count, NPVariant *result) {
    JsObject * jo = (JsObject *) npobj;
    NPString str = { NULL, 0 };
    char buf[512];
    int pos, i;
    bool res;
    char * id = (char *) g_tree_lookup (identifiers, method);
    /*print ("windowClassInvoke\n");*/

    result->type = NPVariantType_Null;
    result->value.objectValue = NULL;

    if (!id) {
        print ("Invoke invalid id\n");
        return false;
    }
    print ("Invoke %s\n", id);
    createJsName (jo, (char **)&str.utf8characters, &str.utf8length);
    pos = snprintf (buf, sizeof (buf), "%s.%s(", str.utf8characters, id);
    free ((char *) str.utf8characters);
    for (i = 0; i < arg_count; i++) {
        char *arg = nsVariant2Str (args + i);
        pos += snprintf (buf + pos, sizeof (buf) - pos, i ? ",%s" : "%s", arg);
        free (arg);
    }
    pos += snprintf (buf + pos, sizeof (buf) - pos, ")");

    str.utf8characters = buf;
    str.utf8length = pos;
    res = nsEvaluate (npp, npobj, &str, result);

    return true;
}

static bool windowClassInvokeDefault (NPObject *npobj,
        const NPVariant *args, uint32_t arg_count, NPVariant *result) {
    (void)npobj; (void)args; (void)arg_count; (void)result;
    print ("windowClassInvokeDefault\n");
    return false;
}

static bool windowClassHasProperty (NPObject *npobj, NPIdentifier name) {
    (void)npobj; (void)name;
    print ("windowClassHasProperty\n");
    return false;
}

static bool windowClassGetProperty (NPObject *npobj, NPIdentifier property,
        NPVariant *result) {
    char * id = (char *) g_tree_lookup (identifiers, property);
    JsObject jo;
    NPString fullname = { NULL, 0 };
    bool res;

    print ("GetProperty %s\n", id);
    result->type = NPVariantType_Null;
    result->value.objectValue = NULL;

    if (!id)
        return false;

    if (!strcmp (((JsObject *) npobj)->name, "window") &&
                !strcmp (id, "top")) {
        result->type = NPVariantType_Object;
        result->value.objectValue = nsRetainObject (js_window);
        return true;
    }

    jo.name = id;
    jo.parent = (JsObject *) npobj;
    createJsName (&jo, (char **)&fullname.utf8characters, &fullname.utf8length);

    res = nsEvaluate (npp, npobj, &fullname, result);

    free ((char *) fullname.utf8characters);

    return res;
}

static bool windowClassSetProperty (NPObject *npobj, NPIdentifier property,
        const NPVariant *value) {
    char *id = (char *) g_tree_lookup (identifiers, property);
    char *script, *var_name, *var_val, *res;
    JsObject jo;
    uint32_t len = 0;

    if (!id)
        return false;

    jo.name = id;
    jo.parent = (JsObject *) npobj;
    createJsName (&jo, &var_name, &len);

    var_val = nsVariant2Str (value);
    script = (char *) malloc (len + strlen (var_val) + 3);
    sprintf (script, "%s=%s;", var_name, var_val);
    free (var_name);
    free (var_val);
    print ("SetProperty %s\n", script);

    res = evaluate (script);
    if (res)
        g_free (res);
    free (script);


    return true;
}

static bool windowClassRemoveProperty (NPObject *npobj, NPIdentifier name) {
    (void)npobj; (void)name;
    print ("windowClassRemoveProperty\n");
    return false;
}


/*----------------%<---------------------------------------------------------*/

static void shutDownPlugin() {
    if (scriptable_peer) {
        nsReleaseObject (scriptable_peer);
        scriptable_peer = NULL;
    }
    if (npShutdown) {
        if (npp) {
            np_funcs.destroy (npp, &saved_data);
            free (npp);
            npp = 0L;
        }
        npShutdown();
        npShutdown = 0;
    }
}

static void readStdin (gpointer p, gint src, GdkInputCondition cond) {
    char *buf_ptr = stream_buf;
    gsize bytes_read = read (src,
            stream_buf + stream_buf_pos,
            sizeof (stream_buf) - stream_buf_pos);
    (void)cond; (void)p;
    if (bytes_read > 0)
        stream_buf_pos += bytes_read;

    /*print ("readStdin %d\n", bytes_read);*/
    while (buf_ptr < stream_buf + stream_buf_pos) {
        uint32_t write_len;
        int32_t bytes_written;

        if (callback_service && !stream_chunk_size) {
            /* read header info */
            if (stream_buf + stream_buf_pos < buf_ptr + 2 * sizeof (uint32_t))
                break; /* need more data */
            current_stream_id = (gpointer)(long)*(uint32_t*)(buf_ptr);
            stream_chunk_size = *((uint32_t *)(buf_ptr + sizeof (uint32_t)));
        /*print ("header %d %d\n",(long)current_stream_id, stream_chunk_size);*/
            buf_ptr += 2 * sizeof (uint32_t);
            if (stream_chunk_size && stream_buf + stream_buf_pos == buf_ptr) {
                stream_buf_pos = 0;
                break; /* only read the header for chunk with data */
            }
        }
        /* feed it to the stream */
        write_len = stream_buf + stream_buf_pos - buf_ptr;
        if (callback_service && write_len > stream_chunk_size)
            write_len = stream_chunk_size;
        bytes_written = writeStream (current_stream_id, buf_ptr, write_len);
        if (bytes_written < 0) {
            print ("couldn't write to stream %d\n", (long)current_stream_id);
            bytes_written = write_len; /* assume stream destroyed, skip */
        }

        /* update chunk status */
        if (bytes_written > 0) {
            buf_ptr += bytes_written;
           /*print ("update chunk %d %d\n", bytes_written, stream_chunk_size);*/
            stream_chunk_size -= bytes_written;
        } else {
            /* FIXME if plugin didn't accept the data retry later, suspend stdin reading */
            break;
        }

    }
    /* update buffer */
    /*print ("buffer written:%d bufpos:%d\n", buf_ptr-stream_buf, stream_buf_pos);*/
    if (stream_buf + stream_buf_pos == buf_ptr) {
        stream_buf_pos = 0;
    } else {
        g_assert (buf_ptr < stream_buf + stream_buf_pos);
        stream_buf_pos -= (stream_buf + stream_buf_pos - buf_ptr);
        memmove (stream_buf, buf_ptr, stream_buf_pos);
    }
    if (bytes_read <= 0) { /* eof of stdin, only for 'cat foo | knpplayer' */
        StreamInfo*si=(StreamInfo*)g_tree_lookup(stream_list,current_stream_id);
        si->reason = NPRES_DONE;
        removeStream (current_stream_id);
        if (stdin_read_watch) {
            gdk_input_remove (stdin_read_watch);
            stdin_read_watch = 0;
        }
    }
}

static int initPlugin (const char *plugin_lib) {
    NPNetscapeFuncs ns_funcs;
    NPError np_err;
    char *pname;

    print ("starting %s with %s\n", plugin_lib, object_url);
    library = g_module_open (plugin_lib, G_MODULE_BIND_LAZY);
    if (!library) {
        print ("failed to load %s\n", plugin_lib);
        return -1;
    }
    if (!g_module_symbol (library,
                "NP_GetMIMEDescription", (gpointer *)&npGetMIMEDescription)) {
        print ("undefined reference to load NP_GetMIMEDescription\n");
        return -1;
    }
    if (!g_module_symbol (library,
                "NP_GetValue", (gpointer *)&npGetValue)) {
        print ("undefined reference to load NP_GetValue\n");
    }
    if (!g_module_symbol (library,
                "NP_Initialize", (gpointer *)&npInitialize)) {
        print ("undefined reference to load NP_Initialize\n");
        return -1;
    }
    if (!g_module_symbol (library,
                "NP_Shutdown", (gpointer *)&npShutdown)) {
        print ("undefined reference to load NP_Shutdown\n");
        return -1;
    }
    print ("startup succeeded %s\n", npGetMIMEDescription ());
    memset (&ns_funcs, 0, sizeof (NPNetscapeFuncs));
    ns_funcs.size = sizeof (NPNetscapeFuncs);
    ns_funcs.version = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR;
    ns_funcs.geturl = nsGetURL;
    ns_funcs.posturl = nsPostURL;
    ns_funcs.requestread = nsRequestRead;
    ns_funcs.newstream = nsNewStream;
    ns_funcs.write = nsWrite;
    ns_funcs.destroystream = nsDestroyStream;
    ns_funcs.status = nsStatus;
    ns_funcs.uagent = nsUserAgent;
    ns_funcs.memalloc = nsAlloc;
    ns_funcs.memfree = nsMemFree;
    ns_funcs.memflush = nsMemFlush;
    ns_funcs.reloadplugins = nsReloadPlugins;
    ns_funcs.getJavaEnv = nsGetJavaEnv;
    ns_funcs.getJavaPeer = nsGetJavaPeer;
    ns_funcs.geturlnotify = nsGetURLNotify;
    ns_funcs.posturlnotify = nsPostURLNotify;
    ns_funcs.getvalue = nsGetValue;
    ns_funcs.setvalue = nsSetValue;
    ns_funcs.invalidaterect = nsInvalidateRect;
    ns_funcs.invalidateregion = nsInvalidateRegion;
    ns_funcs.forceredraw = nsForceRedraw;
    ns_funcs.getstringidentifier = nsGetStringIdentifier;
    ns_funcs.getstringidentifiers = nsGetStringIdentifiers;
    ns_funcs.getintidentifier = nsGetIntIdentifier;
    ns_funcs.identifierisstring = nsIdentifierIsString;
    ns_funcs.utf8fromidentifier = nsUTF8FromIdentifier;
    ns_funcs.intfromidentifier = nsIntFromIdentifier;
    ns_funcs.createobject = nsCreateObject;
    ns_funcs.retainobject = nsRetainObject;
    ns_funcs.releaseobject = nsReleaseObject;
    ns_funcs.invoke = nsInvoke;
    ns_funcs.invokeDefault = nsInvokeDefault;
    ns_funcs.evaluate = nsEvaluate;
    ns_funcs.getproperty = nsGetProperty;
    ns_funcs.setproperty = nsSetProperty;
    ns_funcs.removeproperty = nsRemoveProperty;
    ns_funcs.hasproperty = nsHasProperty;
    ns_funcs.hasmethod = nsHasMethod;
    ns_funcs.releasevariantvalue = nsReleaseVariantValue;
    ns_funcs.setexception = nsSetException;
    ns_funcs.pushpopupsenabledstate = nsPushPopupsEnabledState;
    ns_funcs.poppopupsenabledstate = nsPopPopupsEnabledState;

    js_class.structVersion = NP_CLASS_STRUCT_VERSION;
    js_class.allocate = windowClassAllocate;
    js_class.deallocate = windowClassDeallocate;
    js_class.invalidate = windowClassInvalidate;
    js_class.hasMethod = windowClassHasMethod;
    js_class.invoke = windowClassInvoke;
    js_class.invokeDefault = windowClassInvokeDefault;
    js_class.hasProperty = windowClassHasProperty;
    js_class.getProperty = windowClassGetProperty;
    js_class.setProperty = windowClassSetProperty;
    js_class.removeProperty = windowClassRemoveProperty;

    np_funcs.size = sizeof (NPPluginFuncs);

    np_err = npInitialize (&ns_funcs, &np_funcs);
    if (np_err != NPERR_NO_ERROR) {
        print ("NP_Initialize failure %d\n", np_err);
        npShutdown = 0;
        return -1;
    }
    np_err = npGetValue (NULL, NPPVpluginNameString, &pname);
    if (np_err == NPERR_NO_ERROR)
        print ("NP_GetValue Name %s\n", pname);
    np_err = npGetValue (NULL, NPPVpluginDescriptionString, &pname);
    if (np_err == NPERR_NO_ERROR)
        print ("NP_GetValue Description %s\n", pname);
    return 0;
}

static int newPlugin (NPMIMEType mime, int16 argc, char *argn[], char *argv[]) {
    NPSetWindowCallbackStruct ws_info;
    NPError np_err;
    Display *display;
    int screen;
    int i;
    int needs_xembed;
    uint32_t width = 0, height = 0;

    for (i = 0; i < argc; i++) {
        if (!strcasecmp (argn[i], "width"))
            width = strtol (argv[i], 0L, 10);
        else if (!strcasecmp (argn[i], "height"))
            height = strtol (argv[i], 0L, 10);
    }
    if (width > 0 && height > 0)
        callFunction (-1, "dimension",
                DBUS_TYPE_UINT32, &width, DBUS_TYPE_UINT32, &height,
                DBUS_TYPE_INVALID);

    npp = (NPP_t*)malloc (sizeof (NPP_t));
    memset (npp, 0, sizeof (NPP_t));
    np_err = np_funcs.newp (mime, npp, NP_EMBED, argc, argn, argv, saved_data);
    if (np_err != NPERR_NO_ERROR) {
        print ("NPP_New failure %d %p %p\n", np_err, np_funcs, np_funcs.newp);
        return -1;
    }
    if (np_funcs.getvalue) {
        char *pname;
        void *iid;
        np_err = np_funcs.getvalue ((void*)npp,
                NPPVpluginNameString, (void*)&pname);
        if (np_err == NPERR_NO_ERROR)
            print ("plugin name %s\n", pname);
        np_err = np_funcs.getvalue ((void*)npp,
                NPPVpluginNeedsXEmbed, (void*)&needs_xembed);
        if (np_err != NPERR_NO_ERROR || !needs_xembed) {
            print ("NPP_GetValue NPPVpluginNeedsXEmbed failure %d\n", np_err);
            shutDownPlugin();
            return -1;
        }
        np_err = np_funcs.getvalue ((void*)npp,
                NPPVpluginScriptableIID, (void*)&iid);
        np_err = np_funcs.getvalue ((void*)npp,
                NPPVpluginScriptableNPObject, (void*)&scriptable_peer);
        if (np_err != NPERR_NO_ERROR || !scriptable_peer)
            print ("NPP_GetValue no NPPVpluginScriptableNPObject %d\n", np_err);
    }
    memset (&np_window, 0, sizeof (NPWindow));
    display = gdk_x11_get_default_xdisplay ();
    np_window.x = 0;
    np_window.y = 0;
    np_window.width = 1920;
    np_window.height = 1200;
    np_window.window = (void*)socket_id;
    np_window.type = NPWindowTypeWindow;
    ws_info.type = NP_SETWINDOW;
    screen = DefaultScreen (display);
    ws_info.display = (void*)(long)display;
    ws_info.visual = (void*)(long)DefaultVisual (display, screen);
    ws_info.colormap = DefaultColormap (display, screen);
    ws_info.depth = DefaultDepth (display, screen);
    print ("display %u %dx%d\n", socket_id, width, height);
    np_window.ws_info = (void*)&ws_info;

    GtkAllocation allocation;
    allocation.x = 0;
    allocation.y = 0;
    allocation.width = np_window.width;
    allocation.height = np_window.height;
    gtk_widget_size_allocate (xembed, &allocation);

    np_err = np_funcs.setwindow (npp, &np_window);

    return 0;
}

static gpointer startPlugin (const char *url, const char *mime,
        int argc, char *argn[], char *argv[]) {
    StreamInfo *si;
    if (!npp && (initPlugin (plugin) || newPlugin (mimetype, argc, argn, argv)))
        return 0L;
    si = addStream (url, mime, 0L, 0L, false);
    return si;
}

/*----------------%<---------------------------------------------------------*/

static StreamInfo *getStreamInfo (const char *path, gpointer *stream_id) {
    const char *p = strrchr (path, '_');
    *stream_id = p ? (gpointer) strtol (p+1, NULL, 10) : NULL;
    return (StreamInfo *) g_tree_lookup (stream_list, *stream_id);
}

static DBusHandlerResult dbusFilter (DBusConnection * connection,
        DBusMessage *msg, void * user_data) {
    DBusMessageIter args;
    const char *sender = dbus_message_get_sender (msg);
    const char *iface = "org.kde.kmplayer.backend";
    (void)user_data; (void)connection;
    if (!dbus_message_has_destination (msg, service_name))
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    print ("dbusFilter %s %s\n", sender,dbus_message_get_interface (msg));
    if (dbus_message_is_method_call (msg, iface, "play")) {
        DBusMessageIter ait;
        char *param = 0;
        unsigned int params;
        char **argn = NULL;
        char **argv = NULL;
        int i;
        if (!dbus_message_iter_init (msg, &args) ||
                DBUS_TYPE_STRING != dbus_message_iter_get_arg_type (&args)) {
            g_printerr ("missing url arg");
            return DBUS_HANDLER_RESULT_HANDLED;
        }
        dbus_message_iter_get_basic (&args, &param);
        object_url = g_strdup (param);
        if (!dbus_message_iter_next (&args) ||
                DBUS_TYPE_STRING != dbus_message_iter_get_arg_type (&args)) {
            g_printerr ("missing mimetype arg");
            return DBUS_HANDLER_RESULT_HANDLED;
        }
        dbus_message_iter_get_basic (&args, &param);
        mimetype = g_strdup (param);
        if (!dbus_message_iter_next (&args) ||
                DBUS_TYPE_STRING != dbus_message_iter_get_arg_type (&args)) {
            g_printerr ("missing plugin arg");
            return DBUS_HANDLER_RESULT_HANDLED;
        }
        dbus_message_iter_get_basic (&args, &param);
        plugin = g_strdup (param);
        if (!dbus_message_iter_next (&args) ||
                DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type (&args)) {
            g_printerr ("missing param count arg");
            return DBUS_HANDLER_RESULT_HANDLED;
        }
        dbus_message_iter_get_basic (&args, &params);
        if (params > 0 && params < 100) {
            argn = (char**) malloc (params * sizeof (char *));
            argv = (char**) malloc (params * sizeof (char *));
        }
        if (!dbus_message_iter_next (&args) ||
                DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type (&args)) {
            g_printerr ("missing params array");
            return DBUS_HANDLER_RESULT_HANDLED;
        }
        dbus_message_iter_recurse (&args, &ait);
        for (i = 0; i < params; i++) {
            char *key, *value;
            DBusMessageIter di;
            if (dbus_message_iter_get_arg_type (&ait) != DBUS_TYPE_DICT_ENTRY)
                break;
            dbus_message_iter_recurse (&ait, &di);
            if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type (&di))
                break;
            dbus_message_iter_get_basic (&di, &key);
            if (!dbus_message_iter_next (&di) ||
                    DBUS_TYPE_STRING != dbus_message_iter_get_arg_type (&di))
                break;
            dbus_message_iter_get_basic (&di, &value);
            argn[i] = g_strdup (key);
            argv[i] = g_strdup (value);
            print ("param %d:%s='%s'\n", i + 1, argn[i], value);
            if (!dbus_message_iter_next (&ait))
                params = i + 1;
        }
        print ("play %s %s %s params:%d\n", object_url,
                mimetype ? mimetype : "", plugin, i);
        startPlugin (object_url, mimetype, i, argn, argv);
    } else if (dbus_message_is_method_call (msg, iface, "redirected")) {
        char *url = 0;
        gpointer stream_id;
        StreamInfo *si = getStreamInfo(dbus_message_get_path (msg), &stream_id);
        if (si && dbus_message_iter_init (msg, &args) && 
                DBUS_TYPE_STRING == dbus_message_iter_get_arg_type (&args)) {
            dbus_message_iter_get_basic (&args, &url);
            free (si->url);
            si->url = g_strdup (url);
            si->np_stream.url = si->url;
            print ("redirect %d (had data %d) to %s\n", (long)stream_id, si->called_plugin, url);
        }
    } else if (dbus_message_is_method_call (msg, iface, "eof")) {
        gpointer stream_id;
        StreamInfo *si = getStreamInfo(dbus_message_get_path (msg), &stream_id);
        if (si && dbus_message_iter_init (msg, &args) && 
                DBUS_TYPE_UINT32 == dbus_message_iter_get_arg_type (&args)) {
            dbus_message_iter_get_basic (&args, &si->total);
            if (dbus_message_iter_next (&args) &&
                   DBUS_TYPE_UINT32 == dbus_message_iter_get_arg_type (&args)) {
                dbus_message_iter_get_basic (&args, &si->reason);
                print ("eof %d bytes:%d reason:%d\n", (long)stream_id, si->total, si->reason);
                if (si->stream_pos == si->total || si->destroyed)
                    removeStream (stream_id);
            }
        }
    } else if (dbus_message_is_method_call (msg, iface, "quit")) {
        print ("quit\n");
        shutDownPlugin();
        gtk_main_quit();
    } else if (dbus_message_is_method_call (msg, iface, "streamInfo")) {
        gpointer stream_id;
        StreamInfo *si = getStreamInfo(dbus_message_get_path (msg), &stream_id);
        const char *mime;
        uint32_t length;
        if (si && dbus_message_iter_init (msg, &args) && 
                DBUS_TYPE_STRING == dbus_message_iter_get_arg_type (&args)) {
            dbus_message_iter_get_basic (&args, &mime);
            if (*mime) {
                if (si->mimetype)
                    g_free (si->mimetype);
                si->mimetype = g_strdup (mime);
            }
            if (dbus_message_iter_next (&args) &&
                   DBUS_TYPE_UINT32 == dbus_message_iter_get_arg_type (&args)) {
                dbus_message_iter_get_basic (&args, &length);
                si->np_stream.end = length;
            }
            print ("streamInfo %d size:%d mime:%s\n", (long)stream_id, length,
                    mime ? mime : "");
        }
    } else {
        print ("unknown message\n");
    }
    return DBUS_HANDLER_RESULT_HANDLED;
}

static void callFunction(int stream,const char *func, int first_arg_type, ...) {
    char path[64];
    strncpy (path, callback_path, sizeof (path) -1);
    if (stream > -1) {
        int len = strlen (path);
        snprintf (path + len, sizeof (path) - len, "/stream_%d", stream);
    }
    print ("call %s.%s()\n", path, func);
    if (callback_service) {
        va_list var_args;
        DBusMessage *msg = dbus_message_new_method_call (
                callback_service,
                path,
                "org.kde.kmplayer.callback",
                func);
        if (first_arg_type != DBUS_TYPE_INVALID) {
            va_start (var_args, first_arg_type);
            dbus_message_append_args_valist (msg, first_arg_type, var_args);
            va_end (var_args);
        }
        dbus_message_set_no_reply (msg, TRUE);
        dbus_connection_send (dbus_connection, msg, NULL);
        dbus_message_unref (msg);
        dbus_connection_flush (dbus_connection);
    }
}

static char * evaluate (const char *script) {
    char * ret = NULL;
    print ("evaluate %s", script);
    if (callback_service) {
        DBusMessage *rmsg;
        DBusMessage *msg = dbus_message_new_method_call (
                callback_service,
                callback_path,
                "org.kde.kmplayer.callback",
                "evaluate");
        dbus_message_append_args (
                msg, DBUS_TYPE_STRING, &script, DBUS_TYPE_INVALID);
        rmsg = dbus_connection_send_with_reply_and_block (dbus_connection,
                msg, 2000, NULL);
        if (rmsg) {
            DBusMessageIter it;
            if (dbus_message_iter_init (rmsg, &it) &&
                    DBUS_TYPE_STRING == dbus_message_iter_get_arg_type (&it)) {
                char * param;
                dbus_message_iter_get_basic (&it, &param);
                ret = g_strdup (param);
            }
            dbus_message_unref (rmsg);
            print ("  => %s\n", ret);
        }
        dbus_message_unref (msg);
    } else {
        print ("  => NA\n");
    }
    return ret;
}

/*----------------%<---------------------------------------------------------*/

static void pluginAdded (GtkSocket *socket, gpointer d) {
    /*(void)socket;*/ (void)d;
    print ("pluginAdded\n");
    if (socket->plug_window) {
        gpointer user_data = NULL;
        gdk_window_get_user_data (socket->plug_window, &user_data);
        if (!user_data) {
            /**
             * GtkSocket resets plugins XSelectInput in
             * _gtk_socket_add_window
             *   _gtk_socket_windowing_select_plug_window_input
             **/
            XSelectInput (gdk_x11_get_default_xdisplay (),
                    gdk_x11_drawable_get_xid (socket->plug_window),
                    KeyPressMask | KeyReleaseMask |
                    ButtonPressMask | ButtonReleaseMask |
                    KeymapStateMask |
                    ButtonMotionMask |
                    PointerMotionMask |
                    EnterWindowMask | LeaveWindowMask |
                    FocusChangeMask |
                    ExposureMask |
                    StructureNotifyMask |
                    SubstructureRedirectMask |
                    PropertyChangeMask
                    );
        }
    }
    callFunction (-1, "plugged", DBUS_TYPE_INVALID);
}

static void windowCreatedEvent (GtkWidget *w, gpointer d) {
    (void)d;
    print ("windowCreatedEvent\n");
    socket_id = gtk_socket_get_id (GTK_SOCKET (xembed));
    if (parent_id) {
        print ("windowCreatedEvent %p\n", GTK_PLUG (w)->socket_window);
        if (!GTK_PLUG (w)->socket_window)
            gtk_plug_construct (GTK_PLUG (w), parent_id);
        gdk_window_reparent( w->window,
                GTK_PLUG (w)->socket_window
                    ? GTK_PLUG (w)->socket_window
                    : gdk_window_foreign_new (parent_id),
                0, 0);
        gtk_widget_show_all (w);
        /*XReparentWindow (gdk_x11_drawable_get_xdisplay (w->window),
                gdk_x11_drawable_get_xid (w->window),
                parent_id,
                0, 0);*/
    }
    if (!callback_service) {
        char *argn[] = { "WIDTH", "HEIGHT", "debug", "SRC" };
        char *argv[] = { "440", "330", g_strdup("yes"), g_strdup(object_url) };
        startPlugin (object_url, mimetype, 4, argn, argv);
    }
}

static void embeddedEvent (GtkPlug *plug, gpointer d) {
    (void)plug; (void)d;
    print ("embeddedEvent\n");
}

static gboolean updateDimension (void * p) {
    (void)p;
    if (np_window.window) {
        if (np_window.width != top_w || np_window.height != top_h) {
            np_window.width = top_w;
            np_window.height = top_h;
            np_funcs.setwindow (npp, &np_window);
        }
        update_dimension_timer = 0;
        return 0; /* single shot */
    } else {
        return 1;
    }
}

static gboolean configureEvent(GtkWidget *w, GdkEventConfigure *e, gpointer d) {
    (void)w; (void)d;
    if (e->width != top_w || e->height != top_h) {
        top_w = e->width;
        top_h = e->height;
        if (!update_dimension_timer)
            update_dimension_timer = g_timeout_add (100, updateDimension, NULL);
    }
    return FALSE;
}

static gboolean windowCloseEvent (GtkWidget *w, GdkEvent *e, gpointer d) {
    (void)w; (void)e; (void)d;
    shutDownPlugin();
    return FALSE;
}

static void windowDestroyEvent (GtkWidget *w, gpointer d) {
    (void)w; (void)d;
    gtk_main_quit();
}

static gboolean initPlayer (void * p) {
    GtkWidget *window;
    GdkColormap *color_map;
    GdkColor bg_color;
    (void)p;

    window = callback_service
        ? gtk_plug_new (parent_id)
        : gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (G_OBJECT (window), "delete_event",
            G_CALLBACK (windowCloseEvent), NULL);
    g_signal_connect (G_OBJECT (window), "destroy",
            G_CALLBACK (windowDestroyEvent), NULL);
    g_signal_connect_after (G_OBJECT (window), "realize",
            GTK_SIGNAL_FUNC (windowCreatedEvent), NULL);
    g_signal_connect (G_OBJECT (window), "configure-event",
            GTK_SIGNAL_FUNC (configureEvent), NULL);

    xembed = gtk_socket_new();
    g_signal_connect (G_OBJECT (xembed), "plug-added",
            GTK_SIGNAL_FUNC (pluginAdded), NULL);

    color_map = gdk_colormap_get_system();
    gdk_colormap_query_color (color_map, 0, &bg_color);
    gtk_widget_modify_bg (xembed, GTK_STATE_NORMAL, &bg_color);

    gtk_container_add (GTK_CONTAINER (window), xembed);

    if (!parent_id) {
        gtk_widget_set_size_request (window, 440, 330);
        gtk_widget_show_all (window);
    } else {
        g_signal_connect (G_OBJECT (window), "embedded",
                GTK_SIGNAL_FUNC (embeddedEvent), NULL);
        gtk_widget_set_size_request (window, 1920, 1200);
        gtk_widget_realize (window);
    }

    if (callback_service && callback_path) {
        DBusError dberr;
        const char *serv = "type='method_call',interface='org.kde.kmplayer.backend'";
        char myname[64];

        dbus_error_init (&dberr);
        dbus_connection = dbus_bus_get (DBUS_BUS_SESSION, &dberr);
        if (!dbus_connection) {
            g_printerr ("Failed to open connection to bus: %s\n",
                    dberr.message);
            exit (1);
        }
        g_sprintf (myname, "org.kde.kmplayer.npplayer-%d", getpid ());
        service_name = g_strdup (myname);
        print ("using service %s was '%s'\n", service_name, dbus_bus_get_unique_name (dbus_connection));
        dbus_connection_setup_with_g_main (dbus_connection, 0L);
        dbus_bus_request_name (dbus_connection, service_name, 
                DBUS_NAME_FLAG_REPLACE_EXISTING, &dberr);
        if (dbus_error_is_set (&dberr)) {
            g_printerr ("Failed to register name: %s\n", dberr.message);
            dbus_connection_unref (dbus_connection);
            return -1;
        }
        dbus_bus_add_match (dbus_connection, serv, &dberr);
        if (dbus_error_is_set (&dberr)) {
            g_printerr ("dbus_bus_add_match error: %s\n", dberr.message);
            dbus_connection_unref (dbus_connection);
            return -1;
        }
        dbus_connection_add_filter (dbus_connection, dbusFilter, 0L, 0L);

        /* TODO: remove DBUS_BUS_SESSION and create a private connection */
        callFunction (-1, "running",
                DBUS_TYPE_STRING, &service_name, DBUS_TYPE_INVALID);

        dbus_connection_flush (dbus_connection);
    }
    return 0; /* single shot */
}

int main (int argc, char **argv) {
    int i;

    XInitThreads ();
    g_thread_init (NULL);
    gtk_init (&argc, &argv);

    for (i = 1; i < argc; i++) {
        if (!strcmp (argv[i], "-p") && ++i < argc) {
            plugin = g_strdup (argv[i]);
        } else if (!strcmp (argv[i], "-cb") && ++i < argc) {
            gchar *cb = g_strdup (argv[i]);
            gchar *path = strchr(cb, '/');
            if (path) {
                callback_path = g_strdup (path);
                *path = 0;
            }
            callback_service = g_strdup (cb);
            g_free (cb);
        } else if (!strcmp (argv[i], "-m") && ++i < argc) {
            mimetype = g_strdup (argv[i]);
        } else if (!strcmp (argv [i], "-wid") && ++i < argc) {
            parent_id = strtol (argv[i], 0L, 10);
        } else
            object_url = g_strdup (argv[i]);
    }
    if (!callback_service && !(object_url && mimetype && plugin)) {
        g_fprintf(stderr, "Usage: %s <-m mimetype -p plugin url|-cb service -wid id>\n", argv[0]);
        return 1;
    }

    identifiers = g_tree_new (strcmp);
    stream_list = g_tree_new (streamCompare);

    g_timeout_add (0, initPlayer, NULL);

    fcntl (0, F_SETFL, fcntl (0, F_GETFL) | O_NONBLOCK);

    print ("entering gtk_main\n");

    gtk_main();

    if (dbus_connection)
        dbus_connection_unref (dbus_connection);

    return 0;
}
