nspluginwrapper/src/npw-player.c

2686 lines
68 KiB
C

/*
* npw-player.c - Standalone plugin player
*
* nspluginwrapper (C) 2005-2009 Gwenole Beauchesne
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "sysdeps.h"
#include <errno.h>
#include <dlfcn.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include "rpc.h"
#include "utils.h"
#include "glibcurl.h"
#include "gtk2xtbin.h"
#include "npw-rpc.h"
#define XP_UNIX 1
#define MOZ_X11 1
#include <npapi.h>
#include <npfunctions.h>
#include <npruntime.h>
#define DEBUG 1
#include "debug.h"
// Define to use XEMBED
#define USE_XEMBED 1
#define DEFAULT_WIDTH 640
#define DEFAULT_HEIGHT 480
enum
{
BACKEND_GTK = 1,
};
static gboolean g_verbose = FALSE;
static guint g_backend = BACKEND_GTK;
static guint g_xid = 0;
static guint g_n_plugins = 0;
typedef struct _Plugin Plugin;
typedef struct _PluginDataType PluginDataType;
typedef struct _StreamInstance StreamInstance;
typedef struct _StreamBuffer StreamBuffer;
typedef struct _PlayerApp PlayerApp;
typedef struct _GtkDisplay GtkDisplay;
#define GTK_DISPLAY(display) ((GtkDisplay *)(display))
#define APP_GTK_DISPLAY(app) GTK_DISPLAY ((app)->display)
#define DISPLAY_WIDTH(display) GTK_DISPLAY (display)->width
#define DISPLAY_HEIGHT(display) GTK_DISPLAY (display)->height
struct _GtkDisplay
{
GtkWidget *window;
GtkWidget *canvas;
gint width;
gint height;
};
struct _PlayerApp
{
GtkWidget *window;
gpointer display;
guint width;
guint height;
guint16 mode;
GHashTable *attrs;
Plugin *plugin;
GList *plugins;
};
struct _Plugin
{
gchar *path;
gchar *src;
gchar *mime_type;
void *module;
GList *data_types;
GtkWidget *window;
gboolean use_xembed;
NPP instance;
NPWindow np_window;
NP_InitializeFunc NP_Initialize;
NP_ShutdownFunc NP_Shutdown;
NP_GetValueFunc NP_GetValue;
NP_GetMIMEDescriptionFunc NP_GetMIMEDescription;
NPPluginFuncs plugin_funcs;
NPNetscapeFuncs mozilla_funcs;
};
enum
{
PLUGIN_DATA_TYPE_MIME = 0,
PLUGIN_DATA_TYPE_EXTENSION = 1,
PLUGIN_DATA_TYPE_DESCRIPTION = 2,
PLUGIN_DATA_TYPE_VALUE_COUNT
};
struct _PluginDataType
{
gchar *values[PLUGIN_DATA_TYPE_VALUE_COUNT];
};
enum
{
STREAM_STATUS_IDLE = 0,
STREAM_STATUS_ACTIVE = 1 << 1,
STREAM_STATUS_ERROR = 1 << 2,
STREAM_STATUS_FINISH = 1 << 3,
STREAM_STATUS_DESTROY = 1 << 4
};
enum
{
URI_TYPE_UNKNOWN = 0,
URI_TYPE_FILE,
URI_TYPE_HTTP,
URI_TYPE_FTP
};
struct _StreamInstance
{
NPStream *np_stream;
Plugin *plugin;
gchar *mime_type;
gpointer notify_data;
guint16 stype;
guint uri_type;
gboolean seekable;
guint status;
/* XXX: machinery to download stream then propagate it to the plugin */
CURL *curl_handle;
FILE *temp_file;
gchar *temp_filename;
gint commit_source;
GList *buffers;
guint offset;
};
struct _StreamBuffer
{
guchar *bytes;
guint size;
guint offset;
};
static GList *
plugin_get_data_types (Plugin *plugin);
static gboolean
on_stream_open_cb (gpointer user_data);
static void
on_stream_close_cb (gpointer user_data);
static gboolean
on_stream_commit_cb (gpointer user_data);
static StreamInstance *
stream_new (Plugin *plugin,
const gchar *src, const gchar *type, void *notify_data,
NPError *error);
static void
stream_destroy (StreamInstance *pstream);
static StreamBuffer *
stream_buffer_new (guchar *bytes, gsize nbytes);
static void
stream_buffer_destroy (StreamBuffer *buffer);
/* ====================================================================== */
/* === Utility functions === */
/* ====================================================================== */
static int
uri_type_from_url (const gchar *url)
{
int uri_type = URI_TYPE_UNKNOWN;
if (g_ascii_strncasecmp (url, "file://", 7) == 0)
uri_type = URI_TYPE_FILE;
else if (g_ascii_strncasecmp (url, "http://", 7) == 0)
uri_type = URI_TYPE_HTTP;
else if (g_ascii_strncasecmp (url, "ftp://", 6) == 0)
uri_type = URI_TYPE_FTP;
return uri_type;
}
/* Sanitize URL, e.g. translate local file names to file:// syntax */
static gchar *
sanitize_url (const gchar *uri)
{
/* Local contents has preference if no URI scheme is specified.
*
* This means that if you set src to 'www.example.com/thing.swf' and
* you have a www.example.com directory in the current working
* directory, then the file:// protocol will be prepended. Should
* you need http://, then this has to be specified explicitly.
* However, if there is no www.example.com directory, then libcurl
* will try to resolve this to http:// or whatever.
*/
if (uri_type_from_url (uri) != URI_TYPE_UNKNOWN)
return g_strdup (uri);
if (!g_file_test (uri, G_FILE_TEST_EXISTS))
return g_strdup (uri);
GString *new_uri = g_string_new ("file://");
if (uri[0] != G_DIR_SEPARATOR)
{
gchar *cwd = g_get_current_dir ();
g_string_append (new_uri, cwd);
g_string_append (new_uri, G_DIR_SEPARATOR_S);
g_free (cwd);
}
g_string_append (new_uri, uri);
return g_string_free (new_uri, FALSE);
}
static gchar *
get_mime_type_from_content (guchar *bytes, guint size)
{
gchar *mime_type = NULL;
/* XXX: poor man's MIME type characterisation */
static const guchar gif_sig[] = {0x47, 0x49, 0x46, 0x38};
static const guchar png_sig[] = {0x89, 0x50, 0x4e, 0x47};
static const guchar jpg_sig[] = {0xff, 0xd8, 0xff};
static const guchar bmp_sig[] = {0x42, 0x4d};
static const guchar flv_sig[] = {0x46, 0x4c, 0x56};
static const guchar xml_sig[] = {0x3c, 0x3f, 0x78, 0x6d, 0x6c};
#define MATCH(TYPE) \
(size >= sizeof (TYPE##_sig) && \
memcmp (bytes, TYPE##_sig, sizeof (TYPE##_sig)) == 0)
if (MATCH(png))
mime_type = "image/png";
else if (MATCH(jpg))
mime_type = "image/jpeg";
else if (MATCH(gif))
mime_type = "image/gif";
else if (MATCH(bmp))
mime_type = "image/bmp";
else if (MATCH(flv))
mime_type = "video/x-flv";
else if (MATCH(xml))
mime_type = "text/xml";
else if (size >= 6 && g_ascii_strncasecmp ((gchar *)bytes, "<html>", 6) == 0)
mime_type = "text/html";
#undef MATCH
if (mime_type)
return g_strdup (mime_type);
D(bug("Could not determine MIME type for {%02x,%02x,%02x,%02x,%02x,%02x}\n",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]));
return NULL;
}
static void
player_app_main_quit (void)
{
if (g_backend == BACKEND_GTK)
gtk_main_quit ();
}
#define UNIMPLEMENTED() NP_Unimplemented (__func__, __FILE__, __LINE__)
static void
NP_Unimplemented (const gchar *funcname, const gchar *filename, gint lineno)
{
npw_printf ("WARNING: unimplemented function %s() at %s:%d\n",
funcname, filename, lineno);
}
/* ====================================================================== */
/* === NPRuntime API === */
/* ====================================================================== */
static NPObject *
g_NPN_CreateObject (NPP instance, NPClass *klass)
{
if (instance == NULL)
return NULL;
if (klass == NULL)
return NULL;
NPObject *npobj;
if (klass->allocate)
npobj = klass->allocate (instance, klass);
else
npobj = g_new (NPObject, 1);
if (npobj)
{
npobj->_class = klass;
npobj->referenceCount = 1;
}
return npobj;
}
static NPObject *
g_NPN_RetainObject (NPObject *npobj)
{
if (npobj)
g_atomic_int_add ((volatile gint *)&npobj->referenceCount, 1);
return npobj;
}
static void
g_NPN_ReleaseObject (NPObject *npobj)
{
if (npobj == NULL)
return;
g_atomic_int_add ((volatile gint *)&npobj->referenceCount, -1);
if (g_atomic_int_get ((volatile gint *)&npobj->referenceCount) == 0)
{
if (npobj->_class && npobj->_class->deallocate)
npobj->_class->deallocate (npobj);
else
g_free (npobj);
}
}
static bool
g_NPN_Invoke (NPP instance, NPObject *npobj, NPIdentifier methodName,
const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->invoke)
return false;
return npobj->_class->invoke (npobj, methodName, args, argCount, result);
}
static bool
g_NPN_InvokeDefault (NPP instance, NPObject *npobj,
const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->invokeDefault)
return false;
return npobj->_class->invokeDefault (npobj, args, argCount, result);
}
static bool
g_NPN_Evaluate (NPP instance, NPObject *npobj, NPString *script, NPVariant *result)
{
if (!instance || !npobj)
return false;
if (result)
VOID_TO_NPVARIANT (*result);
if (!script || !script->UTF8Length || !script->UTF8Characters)
return true; // nothing to evaluate
UNIMPLEMENTED();
return false;
}
static bool
g_NPN_GetProperty (NPP instance, NPObject *npobj, NPIdentifier propertyName,
NPVariant *result)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->getProperty)
return false;
return npobj->_class->getProperty (npobj, propertyName, result);
}
static bool
g_NPN_SetProperty (NPP instance, NPObject *npobj, NPIdentifier propertyName,
const NPVariant *value)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->setProperty)
return false;
return npobj->_class->setProperty (npobj, propertyName, value);
}
static bool
g_NPN_RemoveProperty (NPP instance, NPObject *npobj, NPIdentifier propertyName)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->removeProperty)
return false;
return npobj->_class->removeProperty (npobj, propertyName);
}
static bool
g_NPN_HasProperty (NPP instance, NPObject *npobj, NPIdentifier propertyName)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->hasProperty)
return false;
return npobj->_class->hasProperty (npobj, propertyName);
}
static bool
g_NPN_HasMethod (NPP instance, NPObject *npobj, NPIdentifier methodName)
{
if (!instance || !npobj || !npobj->_class || !npobj->_class->hasMethod)
return false;
return npobj->_class->hasProperty (npobj, methodName);
}
static void
g_NPN_SetException (NPObject *npobj, const NPUTF8 *message)
{
UNIMPLEMENTED();
}
static void
g_NPN_ReleaseVariantValue (NPVariant *variant)
{
/* XXX: suppress double free condition in npw-wrapper.c */
switch (variant->type)
{
case NPVariantType_String:
{
NPString *s = &NPVARIANT_TO_STRING (*variant);
if (s->UTF8Characters)
{
g_free ((gpointer)s->UTF8Characters);
s->UTF8Characters = NULL;
}
break;
}
case NPVariantType_Object:
{
NPObject *npobj = NPVARIANT_TO_OBJECT (*variant);
if (npobj)
g_NPN_ReleaseObject (npobj);
break;
}
default:
break;
}
VOID_TO_NPVARIANT (*variant);
}
/* NOTE: g_intern_string() returns linear ids */
#define NP_IDENTIFIER_OBJECT 0x0
#define NP_IDENTIFIER_INT 0x1
#define NP_IDENTIFIER_STRING 0x2
static inline gboolean
is_string_identifier (NPIdentifier id)
{
return (GPOINTER_TO_UINT (id) & 3) == NP_IDENTIFIER_STRING;
}
static inline NPIdentifier
pack_string_identifier (const gchar *str)
{
guint32 id = g_quark_from_string (str);
return GUINT_TO_POINTER ((id << 2) | NP_IDENTIFIER_STRING);
}
static inline const gchar *
unpack_string_identifier (NPIdentifier id)
{
g_return_val_if_fail (is_string_identifier (id), NULL);
return g_quark_to_string (GPOINTER_TO_UINT (id) >> 2);
}
static inline gboolean
is_int_identifier (NPIdentifier id)
{
/* NOTE: the integer value must be 31-bit */
return (GPOINTER_TO_UINT (id) & 1) == NP_IDENTIFIER_INT;
}
static inline NPIdentifier
pack_int_identifier (int32_t value)
{
return GUINT_TO_POINTER ((((uint32_t)value) << 1) | NP_IDENTIFIER_INT);
}
static inline int32_t
unpack_int_identifier (NPIdentifier id)
{
g_return_val_if_fail (is_int_identifier (id), 0);
return ((int32_t)(GPOINTER_TO_INT (id))) >> 1;
}
static NPIdentifier
g_NPN_GetStringIdentifier (const NPUTF8 *name)
{
if (name == NULL)
return NULL;
return pack_string_identifier ((gchar *)name);
}
static void
g_NPN_GetStringIdentifiers (const NPUTF8 **names, int32_t nameCount, NPIdentifier *identifiers)
{
if (names == NULL)
return;
if (identifiers == NULL)
return;
for (int i = 0; i < nameCount; i++)
identifiers[i] = pack_string_identifier ((gchar *)names[i]);
}
static NPIdentifier
g_NPN_GetIntIdentifier (int32_t intid)
{
return pack_int_identifier (intid);
}
static bool
g_NPN_IdentifierIsString (NPIdentifier identifier)
{
return is_string_identifier (identifier);
}
static NPUTF8 *
g_NPN_UTF8FromIdentifier(NPIdentifier identifier)
{
return (NPUTF8 *)unpack_string_identifier (identifier);
}
static int32_t
g_NPN_IntFromIdentifier (NPIdentifier identifier)
{
return unpack_int_identifier (identifier);
}
/* ====================================================================== */
/* === Browser side plug-in API === */
/* ====================================================================== */
static const char *
g_NPN_UserAgent(NPP instance)
{
D(bug("NPN_UserAgent instance %p\n", instance));
return "Mozilla/5.0 (X11; U; Linux i386; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0";
}
static void
g_NPN_Status (NPP instance, const char *message)
{
D(bug("NPN_Status instance %p\n", instance));
UNIMPLEMENTED();
}
static NPError
g_NPN_GetValue (NPP instance, NPNVariable variable, void *value)
{
D(bug("NPN_GetValue instance %p, variable %d [%08x]\n",
instance, variable & 0xffff, variable));
switch (variable) {
#if 0
case NPNVxDisplay:
break;
case NPNVxtAppContext:
break;
#endif
case NPNVToolkit:
*(NPNToolkitType *)value = NPNVGtk2;
break;
case NPNVSupportsXEmbedBool:
*(NPBool *)value = USE_XEMBED;
break;
default:
D(bug("WARNING: unhandled variable %d in NPN_GetValue()\n", variable));
return NPERR_INVALID_PARAM;
}
return NPERR_NO_ERROR;
}
static NPError
g_NPN_SetValue (NPP instance, NPPVariable variable, void *value)
{
D(bug("NPN_SetValue instance %p\n", instance));
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_GetURL (NPP instance, const char *url, const char *target)
{
if (instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
D(bug("NPN_GetURL instance %p\n", instance));
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_GetURLNotify (NPP instance, const char *url, const char *target, void *notifyData)
{
if (instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
D(bug("NPN_GetURLNotify instance %p\n", instance));
if (target == NULL)
{
NPError ret = NPERR_GENERIC_ERROR;
stream_new (instance->ndata, url, NULL, notifyData, &ret);
return ret;
}
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_PostURL (NPP instance, const char *url, const char *target, uint32_t len, const char *buf, NPBool file)
{
if (instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
D(bug("NPN_PostURL instance %p\n", instance));
UNIMPLEMENTED ();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_PostURLNotify (NPP instance, const char *url, const char *target, uint32_t len, const char *buf, NPBool file, void *notifyData)
{
if (instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
D(bug("NPN_PostURLNotify instance %p\n", instance));
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_NewStream (NPP instance, NPMIMEType type, const char *target, NPStream **stream)
{
if (instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
if (stream == NULL)
return NPERR_INVALID_PARAM;
*stream = NULL;
D(bug("NPN_NewStream instance %p\n", instance));
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_DestroyStream (NPP instance, NPStream *stream, NPError reason)
{
if (instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
if (stream == NULL)
return NPERR_INVALID_PARAM;
D(bug("NPN_DestroyStream instance %p, stream %p, reason %s\n",
instance, stream, string_of_NPReason(reason)));
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static NPError
g_NPN_RequestRead (NPStream *stream, NPByteRange *rangeList)
{
if (stream == NULL || stream->ndata == NULL || rangeList == NULL)
return NPERR_INVALID_PARAM;
D(bug("NPN_RequestRead stream=%p\n", stream));
UNIMPLEMENTED();
return NPERR_GENERIC_ERROR;
}
static int32_t
g_NPN_Write (NPP instance, NPStream *stream, int32_t len, void *buf)
{
if (instance == NULL)
return -1;
if (stream == NULL)
return -1;
D(bug("NPN_Write instance %p, stream %p, len %d, buf %p\n", instance, stream, len, buf));
UNIMPLEMENTED();
return -1;
}
static void *
g_NPN_MemAlloc (uint32_t size)
{
D(bug("NPN_MemAlloc size %u\n", size));
return g_malloc (size);
}
static uint32_t
g_NPN_MemFlush (uint32_t size)
{
D(bug("NPN_MemFlush size %u\n", size));
return 0;
}
static void
g_NPN_MemFree (void *ptr)
{
D(bug("NPN_MemFree ptr %p\n", ptr));
g_free (ptr);
}
static void
g_NPN_InvalidateRect (NPP instance, NPRect *invalidRect)
{
D(bug("NPN_InvalidateRect instance %p\n", instance));
UNIMPLEMENTED();
}
static void
g_NPN_InvalidateRegion (NPP instance, NPRegion invalidRegion)
{
D(bug("NPN_InvalidateRegion instance %p\n", instance));
UNIMPLEMENTED();
}
static void
g_NPN_ReloadPlugins(NPBool reloadPages)
{
D(bug("NPN_ReloadPlugins reloadPages %d\n", reloadPages));
UNIMPLEMENTED();
}
static void
g_NPN_ForceRedraw(NPP instance)
{
D(bug("NPN_ForceRedraw instance %p\n", instance));
UNIMPLEMENTED();
}
static void *
g_NPN_GetJavaEnv(void)
{
D(bug("NPN_GetJavaEnv\n"));
return NULL;
}
static void *
g_NPN_GetJavaPeer (NPP instance)
{
D(bug("NPN_GetJavaPeer instance %p\n", instance));
return NULL;
}
static void
g_NPN_PushPopupsEnabledState (NPP instance, NPBool enabled)
{
if (instance == NULL)
return;
D(bug("NPN_PushPopupsEnabledState instance %p, enabled %d\n", instance, enabled));
UNIMPLEMENTED();
}
static void
g_NPN_PopPopupsEnabledState (NPP instance)
{
if (instance == NULL)
return;
D(bug("NPN_PopPopupsEnabledState instance %p\n", instance));
UNIMPLEMENTED();
}
static NPError
g_NP_Initialize (Plugin *plugin)
{
g_return_val_if_fail (plugin != NULL, NPERR_INVALID_FUNCTABLE_ERROR);
memset (&plugin->plugin_funcs, 0, sizeof (plugin->plugin_funcs));
plugin->plugin_funcs.size = sizeof (plugin->plugin_funcs);
plugin->plugin_funcs.version = 19;
memset (&plugin->mozilla_funcs, 0, sizeof (plugin->mozilla_funcs));
plugin->mozilla_funcs.size = sizeof (plugin->mozilla_funcs);
/* TODO: update this to wrap a more recent NPAPI version */
plugin->mozilla_funcs.version = 19; /* (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; */
plugin->mozilla_funcs.geturl = g_NPN_GetURL;
plugin->mozilla_funcs.posturl = g_NPN_PostURL;
plugin->mozilla_funcs.requestread = g_NPN_RequestRead;
plugin->mozilla_funcs.newstream = g_NPN_NewStream;
plugin->mozilla_funcs.write = g_NPN_Write;
plugin->mozilla_funcs.destroystream = g_NPN_DestroyStream;
plugin->mozilla_funcs.status = g_NPN_Status;
plugin->mozilla_funcs.uagent = g_NPN_UserAgent;
plugin->mozilla_funcs.memalloc = g_NPN_MemAlloc;
plugin->mozilla_funcs.memfree = g_NPN_MemFree;
plugin->mozilla_funcs.memflush = g_NPN_MemFlush;
plugin->mozilla_funcs.reloadplugins = g_NPN_ReloadPlugins;
plugin->mozilla_funcs.getJavaEnv = g_NPN_GetJavaEnv;
plugin->mozilla_funcs.getJavaPeer = g_NPN_GetJavaPeer;
plugin->mozilla_funcs.geturlnotify = g_NPN_GetURLNotify;
plugin->mozilla_funcs.posturlnotify = g_NPN_PostURLNotify;
plugin->mozilla_funcs.getvalue = g_NPN_GetValue;
plugin->mozilla_funcs.setvalue = g_NPN_SetValue;
plugin->mozilla_funcs.invalidaterect = g_NPN_InvalidateRect;
plugin->mozilla_funcs.invalidateregion = g_NPN_InvalidateRegion;
plugin->mozilla_funcs.forceredraw = g_NPN_ForceRedraw;
plugin->mozilla_funcs.pushpopupsenabledstate = g_NPN_PushPopupsEnabledState;
plugin->mozilla_funcs.poppopupsenabledstate = g_NPN_PopPopupsEnabledState;
if ((plugin->mozilla_funcs.version & 0xff) >= NPVERS_HAS_NPRUNTIME_SCRIPTING)
{
D(bug(" browser supports scripting through npruntime\n"));
plugin->mozilla_funcs.getstringidentifier = g_NPN_GetStringIdentifier;
plugin->mozilla_funcs.getstringidentifiers = g_NPN_GetStringIdentifiers;
plugin->mozilla_funcs.getintidentifier = g_NPN_GetIntIdentifier;
plugin->mozilla_funcs.identifierisstring = g_NPN_IdentifierIsString;
plugin->mozilla_funcs.utf8fromidentifier = g_NPN_UTF8FromIdentifier;
plugin->mozilla_funcs.intfromidentifier = g_NPN_IntFromIdentifier;
plugin->mozilla_funcs.createobject = g_NPN_CreateObject;
plugin->mozilla_funcs.retainobject = g_NPN_RetainObject;
plugin->mozilla_funcs.releaseobject = g_NPN_ReleaseObject;
plugin->mozilla_funcs.invoke = g_NPN_Invoke;
plugin->mozilla_funcs.invokeDefault = g_NPN_InvokeDefault;
plugin->mozilla_funcs.evaluate = g_NPN_Evaluate;
plugin->mozilla_funcs.getproperty = g_NPN_GetProperty;
plugin->mozilla_funcs.setproperty = g_NPN_SetProperty;
plugin->mozilla_funcs.removeproperty = g_NPN_RemoveProperty;
plugin->mozilla_funcs.hasproperty = g_NPN_HasProperty;
plugin->mozilla_funcs.hasmethod = g_NPN_HasMethod;
plugin->mozilla_funcs.releasevariantvalue = g_NPN_ReleaseVariantValue;
plugin->mozilla_funcs.setexception = g_NPN_SetException;
}
return plugin->NP_Initialize (&plugin->mozilla_funcs, &plugin->plugin_funcs);
}
static void
g_NP_Shutdown (Plugin *plugin)
{
if (plugin && plugin->NP_Shutdown)
plugin->NP_Shutdown ();
}
/* ====================================================================== */
/* === Plug-in side API === */
/* ====================================================================== */
typedef struct _GetAttribsArg GetAttribsArg;
struct _GetAttribsArg
{
GPtrArray *names;
GPtrArray *values;
};
static void
get_attribs_cb (gpointer key, gpointer value, gpointer user_data)
{
GetAttribsArg *arg = (GetAttribsArg *)user_data;
g_ptr_array_add (arg->names, g_strdup (key));
g_ptr_array_add (arg->values, g_strdup (value));
}
static NPError
g_NPP_New (Plugin *plugin, guint16 mode, GHashTable *attrs, guint w, guint h)
{
if (plugin == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
if (attrs == NULL)
return NPERR_INVALID_PARAM;
if ((plugin->instance = g_new0 (NPP_t, 1)) == NULL)
return NPERR_OUT_OF_MEMORY_ERROR;
plugin->instance->ndata = plugin;
GetAttribsArg arg;
gint n_args = g_hash_table_size (attrs);
arg.names = g_ptr_array_sized_new (n_args);
arg.values = g_ptr_array_sized_new (n_args);
g_hash_table_foreach (attrs, get_attribs_cb, &arg);
GString *str = g_string_new (NULL);
g_string_printf (str, "%d", w);
g_ptr_array_add (arg.names, g_strdup ("width"));
g_ptr_array_add (arg.values, g_strdup (str->str));
++n_args;
g_string_printf (str, "%d", h);
g_ptr_array_add (arg.names, g_strdup ("height"));
g_ptr_array_add (arg.values, g_strdup (str->str));
++n_args;
g_string_free (str, TRUE);
g_return_val_if_fail (arg.names->len == n_args, NPERR_GENERIC_ERROR);
g_return_val_if_fail (arg.values->len == n_args, NPERR_GENERIC_ERROR);
NPError ret = plugin->plugin_funcs.newp(
plugin->mime_type,
plugin->instance,
mode,
n_args,
(gchar **)arg.names->pdata,
(gchar **)arg.values->pdata,
NULL);
g_ptr_array_foreach (arg.names, (GFunc)g_free, NULL);
g_ptr_array_free (arg.names, TRUE);
g_ptr_array_foreach (arg.values, (GFunc)g_free, NULL);
g_ptr_array_free (arg.values, TRUE);
// check if XEMBED is to be used
long supports_XEmbed = FALSE;
if (plugin->mozilla_funcs.getvalue)
{
NPError error = plugin->mozilla_funcs.getvalue (NULL, NPNVSupportsXEmbedBool, (void *)&supports_XEmbed);
if (error == NPERR_NO_ERROR && plugin->plugin_funcs.getvalue)
{
long needs_XEmbed = FALSE;
error = plugin->plugin_funcs.getvalue (plugin->instance, NPPVpluginNeedsXEmbed, (void *)&needs_XEmbed);
if (error == NPERR_NO_ERROR)
plugin->use_xembed = supports_XEmbed && needs_XEmbed;
}
}
return ret;
}
static NPError
g_NPP_Destroy (Plugin *plugin)
{
if (plugin == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
if (plugin->plugin_funcs.destroy == NULL)
return NPERR_INVALID_FUNCTABLE_ERROR;
NPSavedData save_data;
NPSavedData *psave_data = &save_data;
NPError ret = plugin->plugin_funcs.destroy(plugin->instance, &psave_data);
if (plugin->instance)
{
g_free (plugin->instance);
plugin->instance = NULL;
}
return ret;
}
static NPError
g_NPP_SetWindow (Plugin *plugin, GtkWidget *parent, gpointer display)
{
if (plugin == NULL || plugin->instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
guint width = DISPLAY_WIDTH (display);
guint height = DISPLAY_HEIGHT (display);
if (plugin->window == NULL)
{
/* Create the window for the first time */
Window xid = 0;
NPSetWindowCallbackStruct *ws_info = NULL;
if (TRUE)
{
if (plugin->use_xembed)
{
if ((plugin->window = gtk_socket_new ()) != NULL)
gtk_container_add (GTK_CONTAINER (parent), plugin->window);
}
else
plugin->window = gtk_xtbin_new (parent->window, NULL);
if (plugin->window == NULL)
return NPERR_GENERIC_ERROR;
gtk_widget_set_size_request (plugin->window, width, height);
gtk_widget_show (plugin->window);
if ((ws_info = g_new0 (NPSetWindowCallbackStruct, 1)) == NULL)
return NPERR_OUT_OF_MEMORY_ERROR;
ws_info->type = 0; // should be NP_SETWINDOW but Mozilla sets it to 0
if (plugin->use_xembed)
{
GdkWindow * const win = plugin->window->window;
ws_info->display = GDK_WINDOW_XDISPLAY (win);
ws_info->visual = GDK_VISUAL_XVISUAL (gdk_drawable_get_visual(win));
ws_info->colormap = GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (win));
ws_info->depth = gdk_drawable_get_visual (win)->depth;
xid = GDK_WINDOW_XWINDOW (win);
}
else
{
ws_info->display = GTK_XTBIN (plugin->window)->xtdisplay;
ws_info->visual = GTK_XTBIN (plugin->window)->xtclient.xtvisual;
ws_info->colormap = GTK_XTBIN (plugin->window)->xtclient.xtcolormap;
ws_info->depth = GTK_XTBIN (plugin->window)->xtclient.xtdepth;
xid = GTK_XTBIN (plugin->window)->xtwindow;
}
}
memset (&plugin->np_window, 0, sizeof (plugin->np_window));
plugin->np_window.type = NPWindowTypeWindow;
plugin->np_window.window = GUINT_TO_POINTER (xid);
plugin->np_window.x = 0;
plugin->np_window.y = 0;
plugin->np_window.width = width;
plugin->np_window.height = height;
plugin->np_window.ws_info = ws_info;
}
else
{
/* Update window size */
plugin->np_window.width = width;
plugin->np_window.height = height;
gtk_widget_set_size_request (plugin->window, width, height);
// XXX: flash9 has some problems when resized too rapidly
if (GTK_IS_XTBIN (plugin->window))
{
// XXX: yes, two of these... for acrobat5!
// or an extra gtk_widget_size_allocate()
gtk_xtbin_resize (plugin->window, width, height);
gtk_xtbin_resize (plugin->window, width, height);
}
}
return plugin->plugin_funcs.setwindow(plugin->instance, &plugin->np_window);
}
static NPError
g_NPP_NewStream (StreamInstance *pstream)
{
if (pstream == NULL)
return NPERR_INVALID_PARAM;
Plugin *plugin = pstream->plugin;
if (plugin == NULL || plugin->instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
return plugin->plugin_funcs.newstream(
plugin->instance,
pstream->mime_type,
pstream->np_stream,
pstream->seekable,
&pstream->stype);
}
static NPError
g_NPP_DestroyStream (StreamInstance *pstream)
{
if (pstream == NULL)
return NPERR_INVALID_PARAM;
Plugin *plugin = pstream->plugin;
if (plugin == NULL || plugin->instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
NPReason reason = NPRES_DONE;
if (pstream->status & STREAM_STATUS_ERROR)
reason = NPRES_NETWORK_ERR;
return plugin->plugin_funcs.destroystream(
plugin->instance,
pstream->np_stream,
reason);
}
static int32_t
g_NPP_WriteReady (StreamInstance *pstream)
{
if (pstream == NULL)
return -1;
Plugin *plugin = pstream->plugin;
if (plugin == NULL || plugin->instance == NULL)
return -1;
return plugin->plugin_funcs.writeready(
plugin->instance,
pstream->np_stream);
}
static int32_t
g_NPP_Write (StreamInstance *pstream, StreamBuffer *buffer, guint32 len)
{
if (pstream == NULL)
return -1;
if (buffer == NULL)
return -1;
Plugin *plugin = pstream->plugin;
if (plugin == NULL || plugin->instance == NULL)
return -1;
return plugin->plugin_funcs.write(
plugin->instance,
pstream->np_stream,
pstream->offset,
len,
buffer->bytes + buffer->offset);
}
static void
g_NPP_StreamAsFile (StreamInstance *pstream, const gchar *filename)
{
if (pstream == NULL)
return;
Plugin *plugin = pstream->plugin;
if (plugin == NULL || plugin->instance == NULL)
return;
if (g_ascii_strncasecmp (filename, "file://", 7) == 0)
filename += 7;
plugin->plugin_funcs.asfile(
plugin->instance,
pstream->np_stream,
filename);
}
static void
g_NPP_URLNotify (StreamInstance *pstream)
{
if (pstream == NULL)
return;
Plugin *plugin = pstream->plugin;
if (plugin == NULL || plugin->instance == NULL)
return;
NPReason reason = NPRES_DONE;
if (pstream->status & STREAM_STATUS_ERROR)
reason = NPRES_NETWORK_ERR;
plugin->plugin_funcs.urlnotify(
plugin->instance,
pstream->np_stream->url,
reason,
pstream->np_stream->notifyData);
}
/* ====================================================================== */
/* === Stream support functions === */
/* ====================================================================== */
static size_t
on_stream_read_nothing_cb (void *ptr, size_t size, size_t nmemb, void *user_data)
{
// don't download anything
return 0;
}
static NPStream *
np_stream_new (const gchar *url, void *notify_data)
{
/* retrieve (remote) file information */
/* XXX: should be incrementally updated in wrapper+viewer through NPP_*() */
CURL *handle = curl_easy_init ();
if (handle == NULL)
return NULL;
curl_easy_setopt (handle, CURLOPT_URL, url);
curl_easy_setopt (handle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt (handle, CURLOPT_WRITEFUNCTION, on_stream_read_nothing_cb);
curl_easy_setopt (handle, CURLOPT_FILETIME, 1);
curl_easy_setopt (handle, CURLOPT_TIMECONDITION, CURL_TIMECOND_LASTMOD);
if (uri_type_from_url (url) == URI_TYPE_HTTP)
curl_easy_setopt (handle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_perform (handle);
double size;
if (curl_easy_getinfo (handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size) != CURLE_OK)
size = 0;
long lastmod;
if (curl_easy_getinfo (handle, CURLINFO_FILETIME, &lastmod) != CURLE_OK ||
lastmod == -1)
lastmod = 0;
curl_easy_cleanup (handle);
NPStream *np_stream = g_new0 (NPStream, 1);
if (np_stream == NULL)
return NULL;
np_stream->url = g_strdup (url);
np_stream->end = size;
np_stream->lastmodified = lastmod;
np_stream->notifyData = notify_data;
return np_stream;
}
static void
np_stream_destroy (NPStream *np_stream)
{
if (np_stream == NULL)
return;
if (np_stream->url)
{
g_free ((gpointer)np_stream->url);
np_stream->url = NULL;
}
g_free (np_stream);
}
static StreamInstance *
stream_new (Plugin *plugin,
const gchar *src, const gchar *type, void *notify_data,
NPError *error)
{
StreamInstance *pstream = NULL;
NPStream *np_stream = NULL;
NPError ret = NPERR_NO_ERROR;
if ((pstream = g_new0 (StreamInstance, 1)) == NULL)
{
ret = NPERR_OUT_OF_MEMORY_ERROR;
goto l_error;
}
if ((np_stream = np_stream_new (src, notify_data)) == NULL)
{
ret = NPERR_OUT_OF_MEMORY_ERROR;
goto l_error;
}
np_stream->ndata = pstream;
pstream->plugin = plugin;
pstream->np_stream = np_stream;
pstream->mime_type = g_strdup (type);
pstream->stype = NP_NORMAL;
pstream->status = STREAM_STATUS_IDLE;
pstream->notify_data = notify_data;
pstream->uri_type = uri_type_from_url (src);
pstream->seekable = 0 && (pstream->uri_type == URI_TYPE_FILE);
if (notify_data == NULL)
{
if ((ret = g_NPP_NewStream (pstream)) != NPERR_NO_ERROR)
goto l_error;
pstream->status |= STREAM_STATUS_ACTIVE;
}
g_idle_add (on_stream_open_cb, pstream);
goto l_return;
l_error:
if (np_stream)
np_stream_destroy (np_stream);
if (pstream)
g_free (pstream);
pstream = NULL;
l_return:
if (error)
*error = ret;
return pstream;
}
static void
stream_destroy (StreamInstance *pstream)
{
if (pstream == NULL)
return;
if (pstream->curl_handle)
{
glibcurl_remove (pstream->curl_handle);
curl_easy_cleanup (pstream->curl_handle);
pstream->curl_handle = NULL;
}
if (pstream->buffers)
{
g_list_foreach (pstream->buffers, (GFunc)stream_buffer_destroy, NULL);
g_list_free (pstream->buffers);
pstream->buffers = NULL;
}
if (pstream->temp_file)
{
/* Close the file prior to calling NPP_StreamAsFile() [Acrobat5] */
fclose (pstream->temp_file);
pstream->temp_file = NULL;
}
if (pstream->status & STREAM_STATUS_DESTROY)
{
if (pstream->stype == NP_ASFILE || pstream->stype == NP_ASFILEONLY)
{
/* temporary files are used only for remote files */
const gchar *fname = pstream->temp_filename;
if (fname == NULL)
fname = pstream->np_stream->url;
g_NPP_StreamAsFile (pstream, fname);
}
g_NPP_DestroyStream (pstream);
if (pstream->notify_data)
g_NPP_URLNotify (pstream);
}
if (pstream->temp_filename)
{
unlink (pstream->temp_filename);
g_free (pstream->temp_filename);
pstream->temp_filename = NULL;
}
if (pstream->mime_type)
{
g_free (pstream->mime_type);
pstream->mime_type = NULL;
}
if (pstream->np_stream)
{
np_stream_destroy (pstream->np_stream);
pstream->np_stream = NULL;
}
g_free (pstream);
}
static void
stream_commit (StreamInstance *pstream)
{
if (pstream == NULL)
return;
if (pstream->commit_source == 0)
pstream->commit_source = g_idle_add (on_stream_commit_cb, pstream);
}
static void
stream_schedule_destroy (StreamInstance *pstream, gboolean now)
{
if (pstream == NULL)
return;
if (now)
pstream->status |= STREAM_STATUS_DESTROY;
else
pstream->status |= STREAM_STATUS_FINISH;
stream_commit (pstream);
}
static inline gboolean
stream_use_npp_write (StreamInstance *pstream)
{
/* If the stream comes from a local file in NP_ASFILEONLY mode,
* the NPP_Write() and NPP_WriteReady() functions are not called.
*/
if (pstream->stype == NP_ASFILEONLY && pstream->uri_type == URI_TYPE_FILE)
return FALSE;
return TRUE;
}
static int32_t
stream_write_ready (StreamInstance *pstream)
{
int32_t write_ready = 0x0fffff;
if (stream_use_npp_write (pstream))
write_ready = g_NPP_WriteReady (pstream);
return write_ready;
}
static int32_t
stream_write (StreamInstance *pstream, StreamBuffer *buffer, guint32 len)
{
switch (pstream->stype)
{
case NP_ASFILE:
case NP_ASFILEONLY:
if (pstream->temp_file)
{
if (fwrite (buffer->bytes + buffer->offset, len, 1, pstream->temp_file) != 1)
return -1;
}
break;
}
if (!stream_use_npp_write (pstream))
return len;
return g_NPP_Write (pstream, buffer, len);
}
static StreamBuffer *
stream_buffer_new (guchar *bytes, gsize nbytes)
{
StreamBuffer *buffer = g_new0 (StreamBuffer, 1);
if (buffer)
{
buffer->bytes = g_memdup (bytes, nbytes);
buffer->size = nbytes;
buffer->offset = 0;
}
return buffer;
}
static void
stream_buffer_destroy (StreamBuffer *buffer)
{
if (buffer == NULL)
return;
if (buffer->bytes)
{
g_free (buffer->bytes);
buffer->bytes = NULL;
}
g_free (buffer);
}
static gboolean
on_stream_commit_cb (gpointer user_data)
{
StreamInstance *pstream = (StreamInstance *)user_data;
if (pstream->buffers)
{
StreamBuffer *buffer = pstream->buffers->data;
if (!(pstream->status & STREAM_STATUS_ACTIVE))
{
/* XXX: determine MIME type (should be done in NPN_GetURL*()...) */
if (pstream->mime_type == NULL)
pstream->mime_type = get_mime_type_from_content (buffer->bytes,
buffer->size);
if (pstream->mime_type == NULL)
pstream->mime_type = g_strdup (pstream->plugin->mime_type);
if (g_NPP_NewStream (pstream) != NPERR_NO_ERROR)
goto force_destroy;
pstream->status |= STREAM_STATUS_ACTIVE;
}
int32_t write_ready = stream_write_ready (pstream);
if (write_ready < 0)
{
/* The plug-in doesn't want the stream, kill all pending buffers */
pstream->status |= STREAM_STATUS_DESTROY;
}
else if (write_ready > 0)
{
int32_t len = MIN (buffer->size - buffer->offset, write_ready);
len = stream_write (pstream, buffer, len);
if (len < 0)
pstream->status |= STREAM_STATUS_ERROR | STREAM_STATUS_DESTROY;
else if (len > 0)
{
if ((buffer->offset += len) >= buffer->size)
{
stream_buffer_destroy (buffer);
pstream->offset += len;
pstream->buffers = g_list_delete_link (pstream->buffers,
pstream->buffers);
}
}
}
}
if (pstream->buffers == NULL && (pstream->status & STREAM_STATUS_FINISH))
pstream->status |= STREAM_STATUS_DESTROY;
if (pstream->buffers == NULL || (pstream->status & STREAM_STATUS_DESTROY))
{
force_destroy:
g_source_remove (pstream->commit_source);
pstream->commit_source = 0;
if (pstream->status & STREAM_STATUS_DESTROY)
stream_destroy (pstream);
return FALSE;
}
return TRUE;
}
static size_t
on_stream_read_cb (void *ptr, size_t size, size_t nmemb, void *user_data)
{
StreamInstance *pstream = (StreamInstance *)user_data;
size_t real_size = size * nmemb;
StreamBuffer *buffer = stream_buffer_new (ptr, real_size);
if (buffer == NULL)
return 0;
pstream->buffers = g_list_append (pstream->buffers, buffer);
stream_commit (pstream);
return real_size;
}
static gboolean
on_stream_open_cb (gpointer user_data)
{
StreamInstance *pstream = (StreamInstance *)user_data;
switch (pstream->stype)
{
case NP_NORMAL:
break;
case NP_ASFILE:
case NP_ASFILEONLY:
/* Technically speaking, the Firefox specs say that the file is
* downloaded to the cache even for local files. However, it
* turns out the file name passed to NPP_StreamAsFile() is not
* the path to the cached file but actually that of the original
* file, if it is a local file.
*
* So, we only use temporary files for remote files. The
* standalone player doesn't handle a persistent cache.
*
* This also means that if you remove the original file while
* viewing it, then you can get unexpected behaviour since the
* plugin won't be using the cached file.
*
* And, in order to be even clearer, the Acrobat Reader plugin expects
* stream->url == fname passed to NPP_StreamAsFile().
*/
if (pstream->uri_type != URI_TYPE_FILE)
{
GError *error = NULL;
int fd = g_file_open_tmp (NULL, &pstream->temp_filename, &error);
if (fd < 0) {
npw_printf ("ERROR: Could not open temporary file: %s\n",
error->message);
g_error_free (error);
return FALSE;
}
pstream->temp_file = fdopen (fd, "w");
if (pstream->temp_file == NULL) {
npw_printf ("ERROR: fdopen failed: %s\n", strerror (errno));
close (fd);
g_free (pstream->temp_filename);
pstream->temp_filename = NULL;
return FALSE;
}
}
break;
default:
UNIMPLEMENTED();
return FALSE;
}
if ((pstream->curl_handle = curl_easy_init ()) == NULL)
npw_printf ("WARNING: could not create CURL stream\n");
CURL * const handle = pstream->curl_handle;
curl_easy_setopt (handle, CURLOPT_URL, pstream->np_stream->url);
curl_easy_setopt (handle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt (handle, CURLOPT_WRITEFUNCTION, on_stream_read_cb);
curl_easy_setopt (handle, CURLOPT_WRITEDATA, pstream);
curl_easy_setopt (handle, CURLOPT_PRIVATE, pstream);
if (pstream->uri_type == URI_TYPE_HTTP)
curl_easy_setopt (handle, CURLOPT_FOLLOWLOCATION, 1);
glibcurl_add (handle);
return FALSE;
}
static void
on_stream_close_cb (gpointer user_data)
{
CURLMsg *msg;
int in_queue;
while ((msg = curl_multi_info_read (glibcurl_handle (), &in_queue)) != NULL)
{
if (msg->msg != CURLMSG_DONE)
continue;
StreamInstance *pstream;
char *pstream_;
CURL *handle = msg->easy_handle;
/* We actually want to get a void* out of here, but for "for
internal reasons", CURL actually returns a char* and gives a
compiler warning otherwise. */
if (curl_easy_getinfo (handle, CURLINFO_PRIVATE, &pstream_) == CURLE_OK)
{
pstream = (StreamInstance *) pstream_;
if (!(pstream->status & STREAM_STATUS_ACTIVE))
{
/* Special case for JavaScript that CURL could not handle */
const gchar * const url = pstream->np_stream->url;
if (g_ascii_strncasecmp (url, "javascript:", 11) == 0)
{
if (pstream->mime_type)
g_free (pstream->mime_type);
pstream->mime_type = g_strdup ("text/html");
/* XXX: this is just a hack to get top.location */
GString *text = NULL;
const gchar *jscode = url + 11;
if (g_ascii_strncasecmp (jscode, "top.location", 12) == 0)
text = g_string_new (pstream->plugin->src);
if (text)
{
bool ok = TRUE;
/* XXX: only allow string concatenation */
jscode += 12;
while (*jscode != '\0')
{
while (g_ascii_isspace (*jscode))
++jscode;
if (*jscode == '\0')
break;
if (*jscode != '+')
{ ok = FALSE; break; }
if (*++jscode == '\0')
{ ok = FALSE; break; }
while (g_ascii_isspace (*jscode))
++jscode;
if (*jscode != '"')
{ ok = FALSE; break; }
if (*++jscode == '\0')
{ ok = FALSE; break; }
const gchar *str = jscode;
while (*jscode != '"')
{
if (*jscode == '\0')
{ ok = FALSE; break; }
++jscode;
}
g_string_append_len (text, str, jscode - str);
++jscode;
}
if (ok)
on_stream_read_cb (text->str, text->len, 1, pstream);
g_string_free (text, TRUE);
}
}
/* XXX: consider the stream in error if it was never started */
if (pstream->buffers == NULL)
pstream->status |= STREAM_STATUS_ERROR;
}
stream_schedule_destroy (pstream, FALSE);
}
}
}
/* ====================================================================== */
/* === Plugin database === */
/* ====================================================================== */
static PluginDataType *
plugin_data_type_new (const gchar *mime, const gchar *ext, const gchar *desc)
{
PluginDataType *data_type = g_new0 (PluginDataType, 1);
if (data_type == NULL)
return NULL;
data_type->values[PLUGIN_DATA_TYPE_MIME] = g_strdup (mime);
data_type->values[PLUGIN_DATA_TYPE_EXTENSION] = g_strdup (ext);
data_type->values[PLUGIN_DATA_TYPE_DESCRIPTION] = g_strdup (desc);
if (g_verbose)
npw_printf ("%-8s %-50s %s\n", ext, mime, desc);
return data_type;
}
static void
plugin_data_type_destroy (PluginDataType *data_type)
{
if (data_type == NULL)
return;
for (gint i = 0; i < PLUGIN_DATA_TYPE_VALUE_COUNT; i++)
{
if (data_type->values[i])
{
g_free (data_type->values[i]);
data_type->values[i] = NULL;
}
}
g_free (data_type);
}
static GList *
plugin_get_data_types (Plugin *plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
if (plugin->data_types)
return plugin->data_types;
const gchar *mime_desc = plugin->NP_GetMIMEDescription ();
if (mime_desc == NULL)
return NULL;
GList *data_types = NULL;
gchar **mime_descs = g_strsplit (mime_desc, ";", -1);
for (gint i = 0; mime_descs[i] != NULL; i++)
{
gchar **mime_parts = g_strsplit (mime_descs[i], ":", -1);
if (mime_parts && mime_parts[0] && mime_parts[1] && mime_parts[2])
{
gchar **extensions = g_strsplit (mime_parts[1], ",", -1);
for (gint j = 0; extensions[j] != NULL; j++)
{
PluginDataType *data_type = plugin_data_type_new (mime_parts[0],
extensions[j],
mime_parts[2]);
if (data_type)
data_types = g_list_prepend (data_types, data_type);
}
g_strfreev (extensions);
}
g_strfreev (mime_parts);
}
g_strfreev (mime_descs);
if (data_types == NULL)
return NULL;
plugin->data_types = g_list_reverse (data_types);
return plugin->data_types;
}
static void plugin_destroy (Plugin *plugin);
static Plugin *
plugin_new (const gchar *path)
{
Plugin *plugin = g_new0 (Plugin, 1);
if (plugin == NULL)
return NULL;
plugin->path = g_strdup (path);
if ((plugin->module = dlopen (path, RTLD_LAZY|RTLD_LOCAL)) == NULL)
goto error;
if (!(plugin->NP_Initialize = dlsym (plugin->module, "NP_Initialize")))
goto error;
if (!(plugin->NP_Shutdown = dlsym (plugin->module, "NP_Shutdown")))
goto error;
if (!(plugin->NP_GetMIMEDescription =
dlsym (plugin->module, "NP_GetMIMEDescription")))
goto error;
if (!(plugin->NP_GetValue = dlsym (plugin->module, "NP_GetValue")))
goto error;
return plugin;
error:
if (plugin)
{
plugin_destroy (plugin);
plugin = NULL;
}
return NULL;
}
static void
plugin_destroy (Plugin *plugin)
{
if (plugin == NULL)
return;
if (plugin->module)
{
/* Intentionally avoid unloading the module. This causes crashes
* as some libraries break when you unload them. In particular,
* Qt has a bug where it will blow away pthread_key_t 0 on
* unload. (In this process, this corresponds to glib's internal
* thread data. Everything in glib breaks when this happens)
* Sadly, Kopete installs this utterly pointless
* "skypebuttons.so" plugin that pulls in Qt while doing almost
* nothing else.
*
* http://bugreports.qt.nokia.com/browse/QTBUG-10861 */
/* dlclose (plugin->module); */
plugin->module = NULL;
}
if (plugin->path)
{
g_free (plugin->path);
plugin->path = NULL;
}
if (plugin->src)
{
g_free (plugin->src);
plugin->src = NULL;
}
if (plugin->mime_type)
{
g_free (plugin->mime_type);
plugin->mime_type = NULL;
}
if (plugin->data_types)
{
g_list_foreach (plugin->data_types, (GFunc)plugin_data_type_destroy, NULL);
g_list_free (plugin->data_types);
plugin->data_types = NULL;
}
if (plugin->np_window.ws_info)
{
g_free (plugin->np_window.ws_info);
plugin->np_window.ws_info = NULL;
}
g_free (plugin);
}
static void
plugin_destroy_if_not (Plugin *plugin, Plugin *plugin_to_keep)
{
if (plugin != plugin_to_keep)
plugin_destroy (plugin);
}
static void
plugin_start (Plugin *plugin, PlayerApp *app)
{
NPError ret;
D(bug("Execute plugin '%s'\n", plugin->path));
ret = g_NP_Initialize (plugin);
if (ret != NPERR_NO_ERROR)
{
npw_printf ("ERROR: could not execute: %s\n", string_of_NPError (ret));
player_app_main_quit ();
return;
}
plugin->src = g_hash_table_lookup (app->attrs, "src");
/* XXX: determine plugin MIME type supported, move elsewhere? */
if (plugin->mime_type == NULL)
{
const gchar *mime_type;
if ((mime_type = g_hash_table_lookup (app->attrs, "type")) != NULL)
plugin->mime_type = g_strdup (mime_type);
}
if (plugin->mime_type == NULL)
{
npw_printf ("ERROR: no MIME type specified for this plugin\n");
player_app_main_quit ();
return;
}
guint width = DISPLAY_WIDTH (app->display);
guint height = DISPLAY_HEIGHT (app->display);
ret = g_NPP_New (plugin, app->mode, app->attrs, width, height);
if (ret != NPERR_NO_ERROR)
{
npw_printf ("ERROR: could not create NPP instance\n");
player_app_main_quit ();
return;
}
ret = g_NPP_SetWindow (plugin, app->window, app->display);
if (ret != NPERR_NO_ERROR)
{
npw_printf ("ERROR: could not create NPP window\n");
player_app_main_quit ();
return;
}
if (plugin->src)
{
NPError error;
StreamInstance *pstream;
pstream = stream_new (plugin, plugin->src, plugin->mime_type, NULL, &error);
if (pstream == NULL || error != NPERR_NO_ERROR)
{
if (pstream)
stream_destroy (pstream);
npw_printf ("ERROR: could not create NPP stream from '%s'\n", plugin->src);
player_app_main_quit ();
return;
}
}
}
static void
plugin_stop (Plugin *plugin)
{
if (plugin == NULL)
return;
if (plugin->plugin_funcs.setwindow)
{
if (plugin->np_window.ws_info)
{
g_free (plugin->np_window.ws_info);
plugin->np_window.ws_info = NULL;
}
/* A NULL handle means the plugin must not do any graphics operation further */
plugin->np_window.window = NULL;
plugin->plugin_funcs.setwindow(
plugin->instance,
&plugin->np_window);
}
g_NPP_Destroy (plugin);
g_NP_Shutdown (plugin);
}
static GList *
g_list_prepend_if_path_exists (GList *dirs, const gchar *path)
{
if (g_file_test (path, G_FILE_TEST_IS_DIR))
dirs = g_list_prepend (dirs, g_strdup (path));
return dirs;
}
static GList *
get_plugin_dirs (void)
{
GList *dirs = NULL;
gchar *path;
path = g_build_filename (g_get_home_dir (), ".mozilla", "plugins", NULL);
dirs = g_list_prepend_if_path_exists (dirs, path);
g_free (path);
const gchar *moz_plugin_path = g_getenv ("MOZ_PLUGIN_PATH");
if (moz_plugin_path)
{
gchar **moz_plugin_paths = g_strsplit (moz_plugin_path, ":", -1);
for (gint i = 0; moz_plugin_paths[i] != NULL; i++)
dirs = g_list_prepend_if_path_exists (dirs, moz_plugin_paths[i]);
g_strfreev (moz_plugin_paths);
}
path = g_build_filename
(G_DIR_SEPARATOR_S "usr", "lib", "mozilla", "plugins", NULL);
dirs = g_list_prepend_if_path_exists (dirs, path);
g_free (path);
path = g_build_filename
(G_DIR_SEPARATOR_S "usr", "lib", "browser", "plugins", NULL);
dirs = g_list_prepend_if_path_exists (dirs, path);
g_free (path);
return dirs;
}
static gboolean
is_npapi_plugin (const gchar *path)
{
void *module = dlopen (path, RTLD_LAZY|RTLD_LOCAL);
if (module == NULL)
{
if (g_verbose)
npw_printf ("WARNING: %s\n", dlerror ());
return FALSE;
}
gboolean is_valid = TRUE;
if (dlsym (module, "NP_Initialize") == NULL)
is_valid = FALSE;
if (dlsym (module, "NP_Shutdown") == NULL)
is_valid = FALSE;
if (dlsym (module, "NP_GetMIMEDescription") == NULL)
is_valid = FALSE;
/* Intentionally avoid unloading the module. This causes crashes as
* some libraries break when you unload them. In particular, Qt has
* a bug where it will blow away pthread_key_t 0 on unload. (In this
* process, this corresponds to glib's internal thread
* data. Everything in glib breaks when this happens) Sadly, Kopete
* installs this utterly pointless "skypebuttons.so" plugin that
* pulls in Qt while doing almost nothing else.
*
* http://bugreports.qt.nokia.com/browse/QTBUG-10861 */
/* dlclose (module); */
return is_valid;
}
static void
player_app_load_plugin (PlayerApp *app, const gchar *path)
{
Plugin *plugin;
if ((plugin = plugin_new (path)) == NULL)
g_error ("could not initialize plugin '%s'", path);
app->plugins = g_list_prepend (app->plugins, plugin);
}
static void
player_app_load_plugins (PlayerApp *app, const gchar *dirname)
{
GDir *dir;
gchar *old_dir = g_get_current_dir ();
const gchar *name;
gchar *path;
if ((dir = g_dir_open (dirname, 0, NULL)) == NULL)
return;
g_chdir (dirname);
while ((name = g_dir_read_name (dir)) != NULL)
{
path = g_build_filename (dirname, name, NULL);
if (is_npapi_plugin (path))
player_app_load_plugin (app, path);
g_free (path);
}
g_dir_close (dir);
g_chdir (old_dir);
g_free (old_dir);
}
typedef struct _FindPluginCustomArg FindPluginCustomArg;
struct _FindPluginCustomArg
{
gint id;
const gchar *value;
gchar **pmime;
};
static gint
find_plugin_custom_cb (PluginDataType *data_type, FindPluginCustomArg *arg)
{
int ret = strcmp (data_type->values[arg->id], arg->value);
if (ret == 0 && arg->pmime)
*arg->pmime = g_strdup (data_type->values[PLUGIN_DATA_TYPE_MIME]);
return ret;
}
static Plugin *
find_plugin_custom (PlayerApp *app, gint id, const gchar *value, gchar **pmime)
{
GList *l;
for (l = app->plugins; l != NULL; l = l->next)
{
Plugin *plugin = (Plugin *)l->data;
FindPluginCustomArg arg;
arg.id = id;
arg.value = value;
arg.pmime = pmime;
if (g_list_find_custom (plugin_get_data_types (plugin),
&arg, (GCompareFunc)find_plugin_custom_cb))
return plugin;
}
return NULL;
}
static inline Plugin *
find_plugin_by_mime (PlayerApp *app, const gchar *mime_type)
{
return find_plugin_custom (app, PLUGIN_DATA_TYPE_MIME, mime_type, NULL);
}
static Plugin *
find_plugin_by_uri (PlayerApp *app, const gchar *uri, gchar **pmime)
{
Plugin *plugin = NULL;
gchar *extension = strrchr (uri, '.');
if (extension)
++extension;
plugin = find_plugin_custom (app, PLUGIN_DATA_TYPE_EXTENSION, extension, pmime);
if (plugin)
return plugin;
/* XXX: load and lookup object MIME type */
return NULL;
}
static void
player_app_destroy (PlayerApp *app)
{
if (app == NULL)
return;
if (app->plugin)
{
plugin_destroy (app->plugin);
app->plugin = NULL;
}
if (app->display)
{
g_free (app->display);
app->display = NULL;
}
g_free (app);
}
static gboolean
player_app_run (PlayerApp *app)
{
GList *plugin_dirs = get_plugin_dirs ();
for (GList *l = plugin_dirs; l != NULL; l = l->next)
{
gchar *plugin_dir = (gchar *)l->data;
player_app_load_plugins (app, plugin_dir);
g_free (plugin_dir);
}
g_list_free (plugin_dirs);
plugin_dirs = NULL;
/* Lookup plugin by MIME type first, then try by object type (from URI) */
gchar *value;
if ((value = g_hash_table_lookup (app->attrs, "type")) != NULL)
app->plugin = find_plugin_by_mime (app, value);
else if ((value = g_hash_table_lookup (app->attrs, "src")) != NULL)
{
gchar *mime_type = NULL;
if ((app->plugin = find_plugin_by_uri (app, value, &mime_type)) != NULL)
{
if (!g_hash_table_lookup_extended (app->attrs, "type", NULL, NULL))
g_hash_table_insert (app->attrs, "type", mime_type);
}
}
else
app->plugin = NULL;
if (app->plugin == NULL)
g_error ("could not find any plugin to use");
/* Free all other plugins */
if (app->plugins)
{
g_list_foreach (app->plugins, (GFunc)plugin_destroy_if_not, app->plugin);
g_list_free (app->plugins);
app->plugins = NULL;
}
plugin_start (app->plugin, app);
return FALSE;
}
static void
player_app_quit (PlayerApp *app)
{
if (app == NULL)
return;
plugin_stop (app->plugin);
}
/* ====================================================================== */
/* === GUI glue === */
/* ====================================================================== */
static void
print_help (const gchar *program_name)
{
g_print ("Usage: %s [option]* [--plugin] [name[=value]]*\n", program_name);
g_print ("\n");
g_print ("Options:\n");
g_print (" -v|--verbose enable verbose mode\n");
g_print (" -f|--fullscreen start in fullscreen mode\n");
g_print (" --xid N embed in window with xid N\n");
g_print ("\n");
g_print ("Common attributes include:\n");
g_print (" embed use NP_EMBED mode\n");
g_print (" full use NP_FULL mode (default)\n");
g_print (" src=URI location (URL) of the object to load\n");
g_print (" type=MIME-TYPE MIME type of the object\n");
g_print (" width=WIDTH width (in pixels)\n");
g_print (" height=HEIGHT height (in pixels)\n");
g_print ("\n");
g_print ("Other attributes will be passed down to the plugin (e.g. flashvars)\n");
g_print ("\n");
}
static gboolean
on_configure_event_cb (GtkWidget *widget,
GdkEventConfigure *event,
PlayerApp *app)
{
if (g_backend != BACKEND_GTK)
return FALSE;
if (event->width == app->width && event->height == app->height)
return FALSE;
// synchronize toplevel window size that actually changed
app->width = event->width;
app->height = event->height;
// notify plugin of the new dimensions
GtkDisplay *display = GTK_DISPLAY (app->display);
display->width = event->width;
display->height = event->height;
g_NPP_SetWindow (app->plugin, app->window, display);
return FALSE;
}
static gboolean
on_window_state_event_cb (GtkWidget *widget,
GdkEventWindowState *event,
PlayerApp *app)
{
if (g_backend != BACKEND_GTK)
return FALSE;
if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
{
/* Fullscreen mode requested */
GtkDisplay *display = GTK_DISPLAY (app->display);
gtk_window_get_size (GTK_WINDOW (widget), &display->width, &display->height);
}
return FALSE;
}
static gboolean
on_window_destroy_cb (GtkWidget *widget, gpointer user_data)
{
if (--g_n_plugins == 0)
gtk_main_quit ();
return FALSE;
}
static GdkFilterReturn
on_client_message_cb (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer user_data)
{
XEvent *xevent = (XEvent *)gdk_xevent;
PlayerApp *app = (PlayerApp *)user_data;
if (app->plugin == NULL || app->plugin->use_xembed)
return GDK_FILTER_CONTINUE;
if (xevent->type != ClientMessage)
return GDK_FILTER_CONTINUE;
if (xevent->xclient.message_type != gdk_x11_get_xatom_by_name ("WM_PROTOCOLS"))
return GDK_FILTER_CONTINUE;
#if 0
/* XXX: avoid gtk2 from resetting the focus to its focus proxy? */
if ((Atom)xevent->xclient.data.l[0] == gdk_x11_get_xatom_by_name ("WM_TAKE_FOCUS"))
return GDK_FILTER_REMOVE;
#endif
return GDK_FILTER_CONTINUE;
}
static void
set_focus_window (PlayerApp *app)
{
if (app->plugin == NULL || app->plugin->use_xembed)
return;
Window focus_window;
int focus_state;
XGetInputFocus (GDK_WINDOW_XDISPLAY (app->window->window), &focus_window, &focus_state);
/* Don't change anything if the focus moved to another window per plugin's request */
/* XXX: in that case, is GDK supposed to know about it for sure (non-NULL GDK window)? */
GdkWindow *gdk_focus_window = gdk_window_lookup (focus_window);
if (gdk_focus_window && gdk_focus_window != app->window->window)
return;
/* XXX: XSetInputFocus() is calling for trouble but I don't know of
* a better way and Firefox is actually doing this...
*/
gdk_error_trap_push ();
XSetInputFocus (GDK_WINDOW_XDISPLAY (app->window->window),
GDK_WINDOW_XWINDOW (app->window->window),
RevertToNone, CurrentTime);
gdk_flush ();
gdk_error_trap_pop ();
/* Give us a chance to filter out WM_TAKE_FOCUS */
gdk_window_add_filter (NULL, on_client_message_cb, app);
}
static void
unset_focus_window (PlayerApp *app)
{
if (app->plugin == NULL || app->plugin->use_xembed)
return;
gdk_window_remove_filter (NULL, on_client_message_cb, app);
}
static GdkFilterReturn
on_window_filter_cb (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer user_data)
{
XEvent *xevent = (XEvent *)gdk_xevent;
PlayerApp *app = (PlayerApp *)user_data;
GtkWidget *widget;
GdkWindow *plugin_window;
GdkFilterReturn ret = GDK_FILTER_CONTINUE;
switch (xevent->type)
{
case CreateNotify:
case ReparentNotify:
/* Make sure we have not messed up the plugin->use_xembed logic */
if (xevent->type == CreateNotify)
plugin_window = gdk_window_lookup (xevent->xcreatewindow.window);
else
{
if (xevent->xreparent.event != xevent->xreparent.parent)
break;
plugin_window = gdk_window_lookup (xevent->xreparent.window);
}
if (plugin_window)
{
user_data = NULL;
gdk_window_get_user_data (plugin_window, &user_data);
widget = GTK_WIDGET (user_data);
if (GTK_IS_XTBIN (widget))
{
g_assert (app->plugin ? !app->plugin->use_xembed : TRUE);
/* Ensure focus is set to the newly created (toplevel)
* window if the pointer turns out to hang over it already
*/
set_focus_window (app);
break;
}
else if (GTK_IS_SOCKET (widget))
{
g_assert (app->plugin ? app->plugin->use_xembed : TRUE);
break;
}
}
ret = GDK_FILTER_REMOVE;
break;
case EnterNotify:
set_focus_window (app);
break;
case DestroyNotify:
gdk_window_remove_filter (app->window->window, on_window_filter_cb, app);
unset_focus_window (app);
break;
default:
break;
}
return ret;
}
static gboolean
on_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
if (event->type != GDK_KEY_PRESS)
return FALSE;
switch (event->keyval)
{
case GDK_Escape:
case GDK_Q:
case GDK_q:
if ((event->state & GDK_CONTROL_MASK) != 0)
return on_window_destroy_cb (widget, user_data);
break;
}
return FALSE;
}
typedef struct _PluginDescriptor PluginDescriptor;
struct _PluginDescriptor
{
guint width;
guint height;
guint16 mode;
GHashTable *attrs;
PlayerApp *app;
};
static PluginDescriptor *
plugin_descriptor_new (void)
{
PluginDescriptor *plugin_desc = g_new0 (PluginDescriptor, 1);
if (plugin_desc == NULL)
return NULL;
plugin_desc->width = DEFAULT_WIDTH;
plugin_desc->height = DEFAULT_HEIGHT;
plugin_desc->mode = NP_FULL;
plugin_desc->attrs = g_hash_table_new (g_str_hash, g_str_equal);
return plugin_desc;
}
int
main (int argc, char *argv[])
{
GPtrArray *plugin_descs = g_ptr_array_new ();
PluginDescriptor *plugin_desc = NULL;
const gchar *title = "nspluginplayer";
gboolean is_fullscreen = FALSE;
guint display_width = DEFAULT_WIDTH;
guint display_height = DEFAULT_HEIGHT;
gint i;
g_thread_init (NULL);
gdk_threads_init ();
glibcurl_init ();
glibcurl_set_callback (on_stream_close_cb, NULL);
gtk_init (&argc, &argv);
for (i = 1; i < argc; i++)
{
const gchar *arg = argv[i];
if (strcmp (arg, "--help") == 0)
{
print_help (argv[0]);
return 0;
}
else if (strcmp (arg, "--verbose") == 0)
g_verbose = TRUE;
else if (strcmp (arg, "--title") == 0)
{
if (++i < argc)
title = argv[i];
}
else if (strcmp (arg, "--gtk") == 0)
g_backend = BACKEND_GTK;
else if (strcmp (arg, "--backend") == 0)
{
if (++i < argc)
{
const gchar *backend_str = argv[i];
if (strcmp (backend_str, "gtk") == 0)
g_backend = BACKEND_GTK;
else
g_error ("unknown backend '%s'", backend_str);
}
}
else if (strcmp (arg, "--window") == 0)
is_fullscreen = FALSE;
else if (strcmp (arg, "--fullscreen") == 0)
is_fullscreen = TRUE;
else if (strcmp (arg, "--xid") == 0)
{
if (++i < argc)
{
g_xid = atoi(argv[i]);
npw_printf ("attaching to xid %d\n", g_xid);
}
}
else if (strcmp (arg, "--plugin") == 0)
{
if (plugin_desc)
g_ptr_array_add (plugin_descs, plugin_desc);
plugin_desc = plugin_descriptor_new ();
}
else
{
if (plugin_desc == NULL)
plugin_desc = plugin_descriptor_new ();
gchar **attrs = g_strsplit (arg, "=", 2);
if (attrs)
{
gchar *name = attrs[0];
gchar *value = attrs[1];
if (value == NULL)
{
/* Only accept "embed" and "full" attributes */
if (g_ascii_strcasecmp (name, "embed") == 0)
plugin_desc->mode = NP_EMBED;
else if (g_ascii_strcasecmp (name, "full") == 0)
plugin_desc->mode = NP_FULL;
else
npw_printf ("WARNING: skip attribute '%s'\n", name);
}
else if (g_ascii_strcasecmp (name, "width") == 0)
plugin_desc->width = atoi (value);
else if (g_ascii_strcasecmp (name, "height") == 0)
plugin_desc->height = atoi (value);
else
{
/* Build up the attrs hash with names in lowercase */
if (g_ascii_strcasecmp (name, "src") == 0)
value = sanitize_url (value);
else
value = g_strdup (value);
g_hash_table_insert (plugin_desc->attrs,
g_ascii_strdown (name, -1), value);
}
}
g_strfreev (attrs);
}
}
if (plugin_desc)
g_ptr_array_add (plugin_descs, plugin_desc);
if (plugin_descs->len == 0)
{
print_help (argv[0]);
return 1;
}
const gchar *backend_str;
switch (g_backend)
{
case BACKEND_GTK:
backend_str = "gtk";
break;
default:
g_error ("unknown backend type (%d)", g_backend);
break;
}
g_n_plugins = plugin_descs->len;
for (i = 0; i < plugin_descs->len; i++)
{
PluginDescriptor *plugin_desc = g_ptr_array_index (plugin_descs, i);
guint width = plugin_desc->width;
guint height = plugin_desc->height;
/* XXX: in Gtk windowed mode, fit window to plugin drawing area size */
if (g_backend == BACKEND_GTK)
{
display_width = plugin_desc->width;
display_height = plugin_desc->height;
}
PlayerApp *app;
if ((app = g_new0 (PlayerApp, 1)) == NULL)
g_error ("could not allocate application data");
app->width = display_width;
app->height = display_height;
app->mode = plugin_desc->mode;
app->attrs = plugin_desc->attrs;
plugin_desc->app = app;
g_timeout_add (100, (GSourceFunc)player_app_run, app);
GtkWidget *window;
if (g_xid)
window = gtk_plug_new ((GdkNativeWindow)g_xid);
else
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
if ((app->window = window) == NULL)
g_error ("could not create toplevel window");
gtk_widget_set_size_request (window, display_width, display_height);
gtk_window_set_title (GTK_WINDOW (window), title);
if (is_fullscreen && !g_xid)
gtk_window_fullscreen (GTK_WINDOW (window));
gtk_widget_show (window);
/* Ensure focus window is this window not gtk's proxy for non XEMBED case */
XWindowAttributes xattrs;
XGetWindowAttributes (GDK_DISPLAY (), GDK_WINDOW_XWINDOW (window->window), &xattrs);
XSelectInput (GDK_DISPLAY (),
GDK_WINDOW_XWINDOW (window->window),
xattrs.your_event_mask | SubstructureNotifyMask);
gdk_window_add_filter (window->window, on_window_filter_cb, app);
XSync (GDK_DISPLAY (), False);
g_signal_connect (window, "destroy",
G_CALLBACK (on_window_destroy_cb), NULL);
g_signal_connect (window, "key-press-event",
G_CALLBACK (on_key_press_event_cb), app);
g_signal_connect (window, "configure-event",
G_CALLBACK (on_configure_event_cb), app);
g_signal_connect (window, "window-state-event",
G_CALLBACK (on_window_state_event_cb), app);
if (g_backend == BACKEND_GTK)
{
GtkWidget *rwindow = NULL;
GtkWidget *canvas = NULL;
GtkDisplay *display = g_new0 (GtkDisplay, 1);
display->window = rwindow;
display->canvas = canvas;
display->width = width;
display->height = height;
app->display = display;
}
}
if (g_backend == BACKEND_GTK)
gtk_main ();
for (i = 0; i < plugin_descs->len; i++)
{
PluginDescriptor *plugin_desc = g_ptr_array_index (plugin_descs, i);
if (plugin_desc->app)
{
player_app_quit (plugin_desc->app);
player_app_destroy (plugin_desc->app);
}
g_free (plugin_desc);
}
g_ptr_array_free (plugin_descs, TRUE);
return 0;
}