/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: playeripc.cpp,v 1.6.2.8 2004/11/23 00:24:25 rggammon Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hlxclib/memory.h"
#include "hlxclib/string.h"
#include "hlxclib/stdio.h"
#include "hlxclib/ctype.h"
#include "hlxclib/errno.h"

#include <unistd.h>
#include <sys/time.h>

#include "hlxclib/sys/types.h"

#include "embeddedapp.h"

#include <gtk/gtk.h>
#include <glib.h>
#include "embddef.h"
#include "hxplayer.h"

#define COMMAND_BUFFER_SIZE 16384

// Avoid using helix-level constructs by defining these:
#define HXR_OK   0x00000000     
#define HXR_FAIL 0x80004005
typedef guint HX_RESULT;

#define PLAYER_IPC_VERSION 1

struct {
    GIOChannel* embedded_command_channel;
    GIOChannel* embedded_callbacks_channel;
    
    gint buf_pos;
    gchar buf[COMMAND_BUFFER_SIZE];
    guint sequence; // old RN code called this ProtocolNumber

    GList* console_attributes_list;
} g_playeripc;


gboolean playeripc_handle_command(GIOChannel *channel, const char *command);
gboolean playeripc_parse_commands(GIOChannel *channel, GIOCondition condition, gpointer data);

static gboolean
handle_ipc_error(GIOChannel*,
                 GIOCondition,
                 gpointer)
{
    /* A broken pipe will not happen here -- see the EOF handler in
       playeripc_parse_commands */
    gtk_main_quit();
    
    return FALSE; // remove event
}

gboolean
playeripc_init(int command_fd, int callbacks_fd)
{
    GError *error = NULL;

    memset(&g_playeripc, 0, sizeof(g_playeripc));

    if(command_fd >= 0)
    {
        g_playeripc.embedded_command_channel = g_io_channel_unix_new(command_fd);
        g_return_val_if_fail(g_playeripc.embedded_command_channel != NULL, FALSE);
        g_io_channel_set_encoding(g_playeripc.embedded_command_channel, NULL, &error);
        if(error)
        {
            g_warning(error->message);            
            g_free(error);        
        }
        g_io_channel_set_buffered(g_playeripc.embedded_command_channel, FALSE);
    }
    if(callbacks_fd >= 0)
    {
        g_playeripc.embedded_callbacks_channel = g_io_channel_unix_new(callbacks_fd);
        g_return_val_if_fail(g_playeripc.embedded_callbacks_channel != NULL, FALSE);
        g_io_channel_set_encoding(g_playeripc.embedded_callbacks_channel, NULL, &error);
        if(error)
        {
            g_warning(error->message);            
            g_free(error);        
        }
        g_io_channel_set_buffered(g_playeripc.embedded_callbacks_channel, FALSE);
    }
    
    g_io_add_watch(g_playeripc.embedded_command_channel,
                   G_IO_IN,
                   playeripc_parse_commands,
                   NULL );

    g_io_add_watch(g_playeripc.embedded_command_channel,
                   (GIOCondition)(G_IO_ERR | G_IO_HUP),
                   handle_ipc_error,
                   NULL );


    return TRUE;
}

/* Parser for plugin IPC protocol */
gboolean
playeripc_parse_commands(GIOChannel *channel,
                           GIOCondition condition,
                           gpointer)
{
    GIOStatus status;
    gsize bytes_read;
    GError* error = NULL;
    char c = 0;
    int fd, result = 0; 
    fd_set fds_read, fds_exception;
    struct timeval timeout;
    
    if(condition != G_IO_IN)
    {
        g_warning("playeripc_parse_commands: condition is %d", condition);
        return TRUE; // don't remove event source
    }
    
    fd = g_io_channel_unix_get_fd(channel);
    
    /* Read from the command fd up to the first '\n' command */
    /* We parse one character at a time because the SetStream command
       does its own thing with reading, and we don't want to eat its data */
    do
    {
        status = g_io_channel_read_chars(channel,
                                         &c,
                                         1,
                                         &bytes_read,
                                         &error);
        if(error)
        {
            g_log(G_LOG_DOMAIN,
                  G_LOG_LEVEL_WARNING,
                  "g_io_channel_read_chars: %s",
                  error->message);
            
            g_free(error);
        }
        
        switch(status)
        {
            case G_IO_STATUS_ERROR:                
            case G_IO_STATUS_EOF:
                gtk_main_quit();
                return FALSE; // remove event
                
            case G_IO_STATUS_AGAIN:
                continue;
            case G_IO_STATUS_NORMAL:
                g_assert(bytes_read == 1);
                g_assert(c != '\0');
                g_playeripc.buf[g_playeripc.buf_pos++] = c;
                break;
            default:
                g_assert(FALSE); /* not reached */
        }

        FD_ZERO(&fds_read);
        FD_ZERO(&fds_exception);

        FD_SET(fd, &fds_read);                                                                                
        FD_SET(fd, &fds_exception);

        /* Return immediately */
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;

        result = select(fd + 1, &fds_read, NULL, &fds_exception, &timeout);
        if(FD_ISSET(fd, &fds_exception) || result < 0)
        {
            /* This should never happen. A closed fd will not cause
               this path to be called. */
            g_warning("Exception in playeripc_parse_commands");
            break;
        }
        
        /* Read until there's no more data, or we get a newline */
    } while(c != '\n' && FD_ISSET(fd, &fds_read));

    
    if(c == '\n')
    {
        g_assert(g_playeripc.buf[g_playeripc.buf_pos - 1] == '\n');
        g_playeripc.buf[g_playeripc.buf_pos - 1] = '\0';
        printf("playeripc: Got command %s\n", g_playeripc.buf);
        playeripc_handle_command(channel, g_playeripc.buf);

        g_playeripc.buf_pos = 0;
    }

    return TRUE; // don't remove the event source.
}


/* RGG: GLib needs one of these... Interval is in ms, in keeping
   with glib standards */
static gboolean
recv_with_timeout(GIOChannel* channel, gchar* buf, gsize len, guint interval)
{
    fd_set fds_read, fds_exception;
    int fd;
    int result;
    struct timeval timeout;
    int interval_sec, interval_usec;
    guint pos = 0;
    gboolean ret = TRUE;

    interval_sec = interval / 1000;
    interval_usec = (interval % 1000) * 1000;
    
    fd = g_io_channel_unix_get_fd(channel);

    do
    {
        FD_ZERO(&fds_read);
        FD_ZERO(&fds_exception);

        FD_SET(fd, &fds_read);                                                                                
        FD_SET(fd, &fds_exception);

        /* XXXRGG: FIXME: Right now, the timeout gets reset each time
           data is received. Don't rely on select() to update timeout. */
        timeout.tv_sec = interval_sec;
        timeout.tv_usec = interval_usec;

        result = select(fd + 1, &fds_read, NULL, &fds_exception, &timeout);
        if(result == 0)
        {
            /* timeout */
            g_warning("Timed out in recv_with_timeout with %d bytes", pos - 1);
            ret = FALSE;
        }
        else if(FD_ISSET(fd, &fds_exception) || result < 0)
        {
            g_warning("Exception in recv_with_timeout");
            ret = FALSE;
        }
        if(FD_ISSET(fd, &fds_read))
        {
            /* Receive data */
            for(;;)
            {
                result = read(fd, buf + pos, len - pos);
                if(result < 0)
                {
                    if(errno == EINTR || errno == EAGAIN)
                    {
                        continue;
                    }
                    else
                    {
                        perror("read");
                        ret = FALSE;
                    }
                }
                else if(result == 0)
                {
                        g_warning("Lost connection to plugin");
                        /* Connection closed */
                        ret = FALSE;
                }
                else
                {
                        pos += result;
                }
                break;
            }                
        }
    } while(ret && pos < len);                                                                               

    return ret;
}

static void
send_ipc(GIOChannel *channel, const gchar* output, gsize size)
{
    GIOStatus status;
    GError *error = NULL;
    gsize bytes_written;
    gsize total_written = 0;
    
    do
    {
        // XXXRGG: Need a timeout here.
        status = g_io_channel_write_chars(channel,
                                          output + total_written,
                                          size - total_written,
                                          &bytes_written,
                                          &error);        
        if(error)
        {
            g_log(G_LOG_DOMAIN,
                  G_LOG_LEVEL_WARNING,
                  "g_io_channel_write_chars: %s",
                  error->message);
            
            g_free(error);
        }
        
        switch(status)
        {
            case G_IO_STATUS_ERROR:                
            case G_IO_STATUS_EOF:
                gtk_main_quit();
                return;
                
            case G_IO_STATUS_AGAIN:
                break;
            case G_IO_STATUS_NORMAL:
                total_written += bytes_written;
                break;
            default:
                g_assert(FALSE); /* not reached */
        }
        
    } while((total_written < size) || (status == G_IO_STATUS_AGAIN));
}

static void
send_reply(GIOChannel *channel, const char *format, ...)
{    
    gchar *command;
    gsize len;
    
    va_list args;
    va_start(args, format);

    command = g_strdup_vprintf(format, args);    
    len = strlen(command) + 1;

    /* We need to add a newline. We could reallocate the string with
       an extra character. Instead, we replace the '\0' (send_ipc
       doesn't need a null-terminated string). */

    command[len - 1] = '\n';
    
    send_ipc(channel, command, len);
    
    g_free(command);
        
    va_end(args);
}

/* This function will modify the string it is passed */
static gchar*
strunquote(gchar* str)
{
    gchar* end;
    gchar* pos;
    
    if(str[0] == '"')
    {
        end = strrchr(str, '"');
        if(end && end != str)
        {
            pos = end + 1;

            while(*pos)
            {
                if(!isspace(*pos))
                {
                    /* There are trailing characters after the closing quote */
                    return str;
                }
            }

            str++;       // get rid of leading quote
            *end = '\0'; // get rid of trailing quote
        }
    }

    return str;
}

static void
window_attributes_set_defaults(HXEmbeddedWindowAttributes *attr)
{
    /* See:
       http://service.real.com/help/library/guides/realone/ScriptingGuide/HTML/realscript.htm
    */
    attr->autogotourl = FALSE;
    attr->autostart = FALSE;
    attr->backgroundcolor = g_strdup("black");
    attr->center = FALSE;
    attr->console = g_strdup("_unique");
    attr->controls = HX_CONTROLS_ALL;
    attr->controls_string = NULL;
    attr->name_flags = HX_NAME_NONE;
    attr->name = NULL;
    attr->nologo = FALSE;
    attr->height = 0;
    attr->loop = FALSE;
    attr->maintainaspect = FALSE;
    attr->numloop = 0;
    attr->prefetch = FALSE;
    attr->region = NULL;
    attr->scriptcallbacks = (HXCallbackFlags)0;
    attr->shuffle = FALSE;
    attr->src = NULL;
    attr->type = NULL;
    attr->width = 0;    
}

static gboolean string_to_bool(const gchar* str)
{
    if((strcasecmp(str, "true") == 0) ||
       (strcmp(str, "1") == 0))
    {
        return TRUE;
    }
    else if((strcasecmp(str, "false") == 0) ||
       (strcmp(str, "0") == 0))
    {
        return FALSE;
    }
    else
    {
        g_warning("Could not convert \"%s\" to boolean\n", str);
    }

    /* Default to false */
    return FALSE;
}


HXCallbackFlags
playeripc_callback_flags_get_from_string(const gchar* str)
{
    static const struct
    {
        const gchar* name;
        HXCallbackFlags flag;
    } lookup[] =
    {
        { "OnAuthorChange",        HX_CALLBACK_ON_AUTHOR_CHANGE            },
        { "OnBuffering",           HX_CALLBACK_ON_BUFFERING                },
        { "OnClipClosed",          HX_CALLBACK_ON_CLIP_CLOSED              },
        { "OnClipOpened",          HX_CALLBACK_ON_CLIP_OPENED              },
        { "OnContacting",          HX_CALLBACK_ON_CONTACTING               },
        { "OnCopyrightChange",     HX_CALLBACK_ON_COPYRIGHT_CHANGE         },
        { "OnErrorMessage",        HX_CALLBACK_ON_ERROR_MESSAGE            },
        { "OnGotoURL",             HX_CALLBACK_ON_GOTO_URL                 },
        { "OnKeyDown",             HX_CALLBACK_ON_KEY_DOWN                 },
        { "OnKeyPress",            HX_CALLBACK_ON_KEY_PRESS                },
        { "OnKeyUp",               HX_CALLBACK_ON_KEY_UP                   },
        { "OnLButtonDown",         HX_CALLBACK_ON_LBUTTON_DOWN             },
        { "OnLButtonUp",           HX_CALLBACK_ON_LBUTTON_UP               },
        { "OnMouseMove",           HX_CALLBACK_ON_MOUSE_MOVE               },
        { "OnMuteChange",          HX_CALLBACK_ON_MUTE_CHANGE              },
        { "OnPlayStateChange",     HX_CALLBACK_ON_PLAY_STATE_CHANGE        },
        { "OnPosLength",           HX_CALLBACK_ON_POS_LENGTH               },
        { "OnPositionChange",      HX_CALLBACK_ON_POSITION_CHANGE          },
        { "OnPostSeek",            HX_CALLBACK_ON_POST_SEEK                },
        { "OnPreFetchComplete",    HX_CALLBACK_ON_PREFETCH_COMPLETE        },
        { "OnPreSeek",             HX_CALLBACK_ON_PRE_SEEK                 },
        { "OnPresentationClosed",  HX_CALLBACK_ON_PRESENTATION_CLOSED      },
        { "OnPresentationOpened",  HX_CALLBACK_ON_PRESENTATION_OPENED      },
        { "OnRButtonDown",         HX_CALLBACK_ON_RBUTTON_DOWN             },
        { "OnRButtonUp",           HX_CALLBACK_ON_RBUTTON_UP               },
        { "OnShowStatus",          HX_CALLBACK_ON_SHOW_STATUS              },
        { "OnStateChange",         HX_CALLBACK_ON_STATE_CHANGE             },
        { "OnTitleChange",         HX_CALLBACK_ON_TITLE_CHANGE             },
        { "OnVolumeChange",        HX_CALLBACK_ON_VOLUME_CHANGE            },
        { "All",                   HX_CALLBACK_ALL                         }
    };                             
    

    gchar** callback_strings;
    gchar** iter;
    HXCallbackFlags callback_flags = (HXCallbackFlags) 0;
    guint i;
    gchar* buf;
    gchar* unquoted_str;

    buf = g_strdup(str);
    unquoted_str = strunquote(hxcommon_strtrim(buf));    
    callback_strings = g_strsplit(unquoted_str, ",", 0);
    iter = callback_strings;
    while(*iter)
    {
        for(i = 0; i < sizeof(lookup) / sizeof(*lookup); i++)
        {
            if(strncasecmp(hxcommon_strtrim(*iter), lookup[i].name, strlen(lookup[i].name)) == 0)
            {
                callback_flags = (HXCallbackFlags)(callback_flags | lookup[i].flag);
            }
        }
        iter++;
    }

    g_strfreev(callback_strings);
    g_free(buf);
    
    return callback_flags;
}

static HXNameFlags
name_flags_get_from_string(const gchar* name)
{
    HXNameFlags flag = HX_NAME_NONE;

    /* XXXRGG: This list may not be complete. Need to
       check Windows player source... */
    if(strcasecmp(name, "PlayControl") == 0)
    {
        flag = HX_NAME_PLAY_CONTROL;
    }
    else if(strcasecmp(name, "PauseControl") == 0)
    {
        flag = HX_NAME_PAUSE_CONTROL;        
    }
    else if(strcasecmp(name, "StopControl") == 0)
    {
        flag = HX_NAME_STOP_CONTROL;
    }

    return flag;
}

/* str is modified */
HXControlFlags
playeripc_control_flags_get_from_string(const gchar* str)
{
    static const struct
    {
        gchar* name;
        HXControlFlags flag;
    } lookup[] =
    {
        { "ControlPanel",    HX_CONTROLS_CONTROL_PANEL      },
        { "FFCtrl",          HX_CONTROLS_FF_CTRL            },
        { "HomeCtrl",        HX_CONTROLS_HOME               },
        { "ImageWindow",     HX_CONTROLS_IMAGE_WINDOW       },
        { "InfoPanel",       HX_CONTROLS_INFO_PANEL         },
        { "InfoVolumePanel", HX_CONTROLS_INFO_VOLUME_PANEL  },
        { "MuteCtrl",        HX_CONTROLS_MUTE_CTRL          },
        { "MuteVolume",      HX_CONTROLS_MUTE_VOLUME        },
        { "PauseButton",     HX_CONTROLS_PAUSE_BUTTON       },
        { "PlayButton",      HX_CONTROLS_PLAY_BUTTON        },
        { "PlayButtonOnly",  HX_CONTROLS_PLAY_BUTTON_ONLY   },
        { "PlayOnlyButton",  HX_CONTROLS_PLAY_BUTTON_ONLY   },
        { "PositionField",   HX_CONTROLS_POSITION_FIELD     },
        { "PositionSlider",  HX_CONTROLS_POSITION_SLIDER    },
        { "RWCtrl",          HX_CONTROLS_RW_CTRL            },
        { "StatusBar",       HX_CONTROLS_STATUS_BAR         },
        { "StatusField",     HX_CONTROLS_STATUS_FIELD       },
        { "StatusPanel",     HX_CONTROLS_STATUS_BAR         },            
        { "StopButton",      HX_CONTROLS_STOP_BUTTON        },
        { "TACCtrl",         HX_CONTROLS_TAC                },
        { "VolumeSlider",    HX_CONTROLS_VOLUME_SLIDER      },
        { "All",             HX_CONTROLS_ALL                }
    };                             
    

    gchar** controls_strings;
    gchar** iter;
    HXControlFlags control_flags = (HXControlFlags) 0;
    guint i;
    gchar* unquoted_str;
    gchar* buf;

    buf = g_strdup(str);
    unquoted_str = strunquote(hxcommon_strtrim(buf));
    controls_strings = g_strsplit(unquoted_str, ",", 0);
    iter = controls_strings;
    while(*iter)
    {
        for(i = 0; i < sizeof(lookup) / sizeof(*lookup); i++)
        {
            if(strncasecmp(hxcommon_strtrim(*iter), lookup[i].name, strlen(lookup[i].name)) == 0)
            {
                control_flags = (HXControlFlags)(control_flags | lookup[i].flag);
                break;
            }
        }
        iter++;
    }

    if((int)control_flags == 0)
    {
        /* We have a controls tag, but it is specifying an unknown control */
        g_warning("Got unknown control: %s", str);
        control_flags = HX_CONTROLS_UNKNOWN;
    }
    
    g_strfreev(controls_strings);
    g_free(buf);
    
    return control_flags;
    
}

static void
window_attributes_get_from_args(HXEmbeddedWindowAttributes *attr, gchar** pairs)
{
    gchar** pairs_iter;

    for(pairs_iter = pairs; *pairs_iter; pairs_iter++)
    {
        gchar* key = NULL;
        gchar* val = NULL;

        key = *pairs_iter;
        val = strchr(key, '=');
        if(!val)
        {
            g_warning("Got unknown key=val \"%s\"", *pairs_iter);
        }
        else
        {
            /* Otherwise we have a valid key and val */
            *val++ = '\0'; // terminate key
            
            key = hxcommon_strtrim(key);
            val = strunquote(hxcommon_strtrim(val));
            
            if     (strcasecmp(key, "autogotourl") == 0)
            {
                attr->autogotourl = string_to_bool(val);
            }
            else if(strcasecmp(key, "autostart") == 0)
            {
                attr->autostart = string_to_bool(val);
            }
            else if(strcasecmp(key, "backgroundcolor") == 0)
            {
                if(attr->backgroundcolor)
                {
                    g_free(attr->backgroundcolor);
                }
                attr->backgroundcolor = g_strdup(val);
            }
            else if(strcasecmp(key, "center") == 0)
            {
                attr->center = string_to_bool(val);                
            }
            else if(strcasecmp(key, "console") == 0)
            {
                if(attr->console)
                {
                    g_free(attr->console);
                }
                attr->console = g_strdup(val);
            }
            else if(strcasecmp(key, "consoleevents") == 0)
            {
                attr->consoleevents = string_to_bool(val);                
            }
            else if(strcasecmp(key, "controls") == 0)
            {
                if(attr->controls_string)
                {
                    g_free(attr->controls_string);
                }
                attr->controls_string = g_strdup(val);
                attr->controls = playeripc_control_flags_get_from_string(val);
            }
            else if(strcasecmp(key, "height") == 0)
            {
                sscanf(val, "%d", &attr->height);
            }
            else if(strcasecmp(key, "loop") == 0)
            {
                attr->loop = string_to_bool(val);
            }
            else if(strcasecmp(key, "maintainaspect") == 0)
            {
                attr->maintainaspect = string_to_bool(val);                
            }
            else if(strcasecmp(key, "name") == 0)
            {
                /* Normally, name is strictly for use with javascript/dom
                   stuff. Some (undocumented) values seem to also change
                   the controls layout. Handle these values here. */
                
                attr->name_flags = name_flags_get_from_string(val);

                if(attr->name)
                {
                    g_free(attr->name);
                }
                attr->name = g_strdup(val);
            }
            else if(strcasecmp(key, "nojava") == 0)
            {
                /* No longer applicable to Mozilla plugins */
            }
            else if(strcasecmp(key, "nologo") == 0)
            {
                attr->nologo = string_to_bool(val);
            }
            else if(strcasecmp(key, "numloop") == 0)
            {
                sscanf(val, "%d", &attr->numloop);
            }
            else if(strcasecmp(key, "prefetch") == 0)
            {
                attr->prefetch = string_to_bool(val);
            }
            else if(strcasecmp(key, "region") == 0)
            {
                /* XXXRGG: Implement me */
                g_warning("REGION attribute unimplemented");
            }
            else if(strcasecmp(key, "scriptcallbacks") == 0)
            {
                attr->scriptcallbacks = playeripc_callback_flags_get_from_string(val);
            }
            else if(strcasecmp(key, "shuffle") == 0)
            {
                attr->shuffle = string_to_bool(val);
            }
            else if(strcasecmp(key, "src") == 0)
            {
                if(attr->src)
                {
                    g_free(attr->src);
                }
                attr->src = g_strdup(val);
            }
            else if(strcasecmp(key, "type") == 0)
            {
                if(attr->type)
                {
                    g_free(attr->type);
                }
                attr->type = g_strdup(val);
            }
            else if(strcasecmp(key, "width") == 0)
            {
                sscanf(val, "%d", &attr->width);
            }
            else if(strcasecmp(key, "nolabels") == 0)
            {
                /* Ignore the nolabels attribute -- it was deprecated in RP5,
                   and not supported in G2. */
            }
            else if(strcasecmp(key, "pluginspage") == 0 ||
                    strcasecmp(key, "pluginurl"  ) == 0 ||
                    strcasecmp(key, "align"      ) == 0 ||
                    strcasecmp(key, "border"     ) == 0 ||
                    strcasecmp(key, "frameborder") == 0 ||
                    strcasecmp(key, "units"      ) == 0 ||
                    strcasecmp(key, "hidden"     ) == 0 ||
                    strcasecmp(key, "hspace"     ) == 0 ||
                    strcasecmp(key, "vspace"     ) == 0 ||
                    strcasecmp(key, "palette"    ) == 0)
            {
                /* These are all attributes that are handled by the browser.
                   We ignore them. */
                
                // XXXRGG: We should actually be using units to handle the
                // conversion of height/width to pixels if units is en.
                // How do we calculate pixels from point size?
            }
            else
            {
                g_warning("Ignoring unknown attribute %s", key);
            }
        }
    }
}

/* Handle all the commands that work on a player object
   (everything but Embed) */
static gboolean
handle_window_command(HXEmbeddedWindow* window, GIOChannel *channel, gint argc, gchar** argv)
{
    gint result = TRUE;

    if (strcmp("SetPlayerUINT32Prop", argv[0]) == 0 && argc == 4)
    {
        gchar* prop;
        guint val;

        prop = argv[2];
        result = sscanf(argv[3], "%d", &val);
                
        if(result)
        {
            result = hxembedded_window_set_uint_property(window, prop, val);
        }

        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("SetPlayerStringProp", argv[0]) == 0 && argc == 4)
    {
        gchar* prop;
        gchar* val;

        prop = argv[2];
        val = argv[3];
            
        result = hxembedded_window_set_string_property(window, prop, val);
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("GetPlayerUINT32Prop", argv[0]) == 0 && argc == 3)
    {
        gchar* prop;
        guint val = 0;
            
        prop = argv[2];
            
        result = hxembedded_window_get_uint_property(window, prop, &val);        
        send_reply(channel, "%d, %d", result? HXR_OK: HXR_FAIL, val);
    }
    else if (strcmp("GetPlayerStringProp", argv[0]) == 0 && argc == 3)
    {
        gchar* prop;
        gchar* val = NULL;

        prop = argv[2];
            
        result = hxembedded_window_get_string_property(window, prop, &val, 0);
        if(result)
        {
            send_reply(channel, "%d, \"%s\"", HXR_OK, val);
            g_free(val);
        }
        else
        {
            send_reply(channel, "%d, \"\"", HXR_FAIL);
        }
    }
    else if (strcmp("GetEntryStringProp", argv[0]) == 0 && argc == 4)
    {
        gchar* prop;
        gchar* val = NULL;
        guint index;

        prop = argv[2];
        result = sscanf(argv[3], "%d", &index);

        if(result)
        {
            result = hxembedded_window_get_entry_string_property(window, prop, index, &val, 0);
        }
        
        if(result)
        {
            send_reply(channel, "%d, \"%s\"", HXR_OK, val);
            g_free(val);
        }
        else
        {
            send_reply(channel, "%d, \"\"", HXR_FAIL);
        }
    }
    else if (strcmp("Play", argv[0]) == 0 && argc == 2)
    {
        hxembedded_window_play(window);
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("Seek", argv[0]) == 0 && argc == 3)
    {
        guint pos;

        result = sscanf(argv[2], "%d", &pos);            

        if(result)
        {
            hxembedded_window_start_seeking(window);
            hxembedded_window_stop_seeking(window, pos);
            send_reply(channel, "%d", HXR_OK);
        }
        else
        {
            send_reply(channel, "%d", HXR_FAIL);
        }
    }
    else if (strcmp("PlayPause", argv[0]) == 0 && argc == 2)
    {
        HXContentStateType state;
            
        state = hxembedded_window_get_content_state(window);

        switch(state)
        {
            case HX_CONTENT_STATE_STOPPED: 
            case HX_CONTENT_STATE_PAUSED:
            case HX_CONTENT_STATE_NOT_LOADED:
                hxembedded_window_play(window);
                break;
            case HX_CONTENT_STATE_PLAYING:
            case HX_CONTENT_STATE_BUFFERING:
                hxembedded_window_pause(window);
                break;
            default:
                result = FALSE;
                break;
        }
        
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("Pause", argv[0]) == 0 && argc == 2)
    {
        hxembedded_window_pause(window);
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("Stop", argv[0]) == 0 && argc == 2)
    {
        hxembedded_window_stop(window);
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("Suspend", argv[0]) == 0 && argc == 2)
    {
        // XXXRGG: What's suspend supposed to do?
        hxembedded_window_pause(window);
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("Resume", argv[0]) == 0 && argc == 2)
    {
        hxembedded_window_play(window);
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("UnsetWindow", argv[0]) == 0 && argc == 2)
    {
        result = hxembedded_window_unset_window(window);
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("SetXID", argv[0]) == 0 && argc == 12)
    {
        XID xid;
            
        /* SetWindow passes us various dimensions.
           The player will figure them out from the XID, though,
           so we ignore them. */

        result = sscanf(argv[2], "%d", (int*)&xid);

        if(result)
        {
            result = hxembedded_window_set_window(window, xid, 0);
        }
        
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("SetWindow", argv[0]) == 0 && argc == 12)
    {
        guint socket_id;
            
        result = sscanf(argv[2], "%d", &socket_id);            

        if(result)
        {
            result = hxembedded_window_set_window(window, 0, socket_id);
        }
        
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("NewStream", argv[0]) == 0 && argc == 6)
    {
        gchar* mime_type;
        gchar* url;
        guint stream_id;
        guint stream_length;
        
        result = sscanf(argv[2], "%d", &stream_id);
        url = argv[3];
        mime_type = argv[4];
        result = sscanf(argv[5], "%d", &stream_length);

        /* If stream_id is 0, mozilla has called the plugin's NewStream method
           because it has encountered a <embed> tag. The plugin will abort this
           stream by returning NPERR_NO_DATA, but it still calls this method
           with stream as NULL in order to give us the content and mime type.
           We will use this content and mime type later when we start playback
           through a GetURL call. */
        
        if(stream_id)
        {
            hxembedded_window_new_stream(window, stream_id, url, mime_type, stream_length);
        }
        else
        {
            hxembedded_window_set_src_url(window, url);
        }
        
        send_reply(channel, "%d", HXR_OK);
    }
    else if (strcmp("StreamData", argv[0]) == 0 && argc == 4)
    {
        gchar* buf;
        guint buf_len;
        guint stream_id;
        
        result = sscanf(argv[2], "%d", &stream_id);

        if(result)
        {
            result = sscanf(argv[3], "%d", &buf_len);
        }

        if(result && buf_len > 0)
        {
            buf = (gchar*)g_try_malloc(buf_len + 1);
            result = (buf != NULL);
            
            if(result)
            {
                /* Give the plugin a 1000 ms inactivity timeout */
                result = recv_with_timeout(channel, buf, buf_len, 1000);
                buf[buf_len] = '\0';
            }
            
            if(result)
            {
                hxembedded_window_stream_data(window, stream_id, (gchar*)buf, buf_len);
            }

            g_free(buf);
        }
        
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("StreamDone", argv[0]) == 0 && argc == 3)
    {
        guint stream_id;
        
        result = sscanf(argv[2], "%d", &stream_id);

        hxembedded_window_stream_done(window, stream_id);        
        
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else if (strcmp("SetStream", argv[0]) == 0 && argc == 4)
    {
        g_warning("Deprecated command SetStream called");
        send_reply(channel, "%d", HXR_FAIL);
    }
    else if (strcmp("Browser", argv[0]) == 0 && argc == 5)
    {
        const gchar* user_agent;
        gint has_callbacks;
        gint has_xembed;

        user_agent = argv[2];
                
        result = sscanf(argv[3], "%d", &has_callbacks);

        if(result)
        {
            result = sscanf(argv[4], "%d", &has_xembed);
        }

        if(result)
        {
            hxembedded_window_set_browser_info(window,
                                               user_agent,
                                               has_callbacks,
                                               has_xembed);
        }
        
        send_reply(channel, "%d", result? HXR_OK: HXR_FAIL);
    }
    else
    {        
        send_reply(channel, "%d", HXR_FAIL);                        
        result = FALSE;
    }        

    return result;
}

/* Returns whether or not the command parsed correctly. */
gboolean
playeripc_handle_command(GIOChannel *channel, const char *command)
{
    gchar** argv;
    gint argc = 0;
    GError *error = NULL;
    gboolean parse_result;
    gboolean ret = TRUE;
    
    parse_result = g_shell_parse_argv(command, &argc, &argv, &error);
    if(error)
    {
        g_warning(error->message);
        g_free(error);
        return FALSE;
    }
    
    g_return_val_if_fail(parse_result, FALSE);
    
    if(strcmp("Embed", argv[0]) == 0)
    {
        HXEmbeddedWindowAttributes attr;

        window_attributes_set_defaults(&attr);
        window_attributes_get_from_args(&attr, &argv[1]);

        guint window_id = hxembedded_window_attach(&attr);
        
	send_reply(channel, "%d", window_id);
    }
    else if(strcmp("Version", argv[0]) == 0)
    {
        guint plugin_ipc_version = 0;
        sscanf(argv[1], "%d", &plugin_ipc_version);

        if(plugin_ipc_version > PLAYER_IPC_VERSION)
        {
            g_warning("plugin version %d > player version %d",
                      plugin_ipc_version, PLAYER_IPC_VERSION);
        }
        
	send_reply(channel, "%d, %d", HXR_OK, PLAYER_IPC_VERSION);
    }
    else if(strcmp("Shutdown", argv[0]) == 0)
    {
	send_reply(channel, "%d", HXR_OK);
        // XXXRGG: be more graceful
        gtk_main_quit();
    }
    else
    {
        /* Get the window id from argv[1] */
        guint window_id;
        HXEmbeddedWindow* window;

        if(sscanf(argv[1], "%d", &window_id))
        {        
            window = hxembedded_window_get_from_id(window_id);
            if(window)
            {
                ret = handle_window_command(window, channel, argc, argv);
            }
            else
            {
                g_warning("Bad window id %d", window_id);
            }
        }
        else
        {
            g_warning("Bad window id \"%s\"", argv[1]);
        }
    }    

    g_strfreev(argv);

    if(!ret)
    {
        g_warning("Error parsing command \"%s\"", command);
    }

    return ret;
}

void
playeripc_get_url(HXEmbeddedWindow* window, const gchar* url, const gchar* target)
{
    gchar* cmd;
    guint window_id;

    g_return_if_fail(url != NULL);
    
    if(!target)
    {
        target = "\"\"";
    }
    
    window_id = hxembedded_window_get_id(window);
    
    cmd = g_strdup_printf("GetURL %d \"%s\" %s\n", window_id, url, target);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);
}

/* Javascript callbacks */
void
playeripc_on_author_change(HXEmbeddedWindow* window,
                           const gchar*      /* unused */,
                           GValue*           author_value)
{
    gchar* cmd;
    const gchar* author_name = g_value_get_string(author_value);
    const gchar* name;

    name = hxembedded_window_get_name(window);
    
    gchar* author_name_quoted;
    author_name_quoted = g_shell_quote(author_name);
    
    cmd = g_strdup_printf("Callback %s OnAuthorChange %s\n",
                          name, author_name_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);
    g_free(author_name_quoted);
}

void
playeripc_on_buffering(HXEmbeddedWindow*    window,
                       HXBufferingReason    reason,
                       guint                percent_complete)
{
    gchar *cmd;
    const gchar* name;

    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnBuffering %d,%d\n",
                          name, (int)reason, percent_complete);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);
}

void
playeripc_on_clip_closed(HXEmbeddedWindow* window)
{
    gchar *cmd;
    const gchar* name;

    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnClipClosed\n",
                          name);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);
}

void
playeripc_on_clip_opened(HXEmbeddedWindow* window,
                         const gchar*      short_clip_name,
                         const gchar*      url)
{
    gchar *cmd;
    const gchar* name;

    name = hxembedded_window_get_name(window);

    gchar* short_clip_name_quoted = g_shell_quote(short_clip_name);
    gchar* url_quoted = g_shell_quote(url);    
    
    cmd = g_strdup_printf("Callback %s OnClipOpened %s,%s\n",
                          name, short_clip_name_quoted, url_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(short_clip_name_quoted);
    g_free(url_quoted);
}

void
playeripc_on_contacting(HXEmbeddedWindow* window,
                        const gchar*      contacting_text)
{
    gchar *cmd;
    gchar* contacting_text_quoted = g_shell_quote(contacting_text);    
    const gchar* name;

    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnContacting %s\n",
                          name, contacting_text_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(contacting_text_quoted);
}

void
playeripc_on_copyright_change(HXEmbeddedWindow* window,
                              const char* /* unused */,
                              GValue* copyright_value)
{
    gchar *cmd;
    const gchar* name;

    name = hxembedded_window_get_name(window);

    const gchar* copyright = g_value_get_string(copyright_value);

    gchar* copyright_quoted = g_shell_quote(copyright);    
    
    cmd = g_strdup_printf("Callback %s OnCopyrightChange %s\n",
                          name, copyright_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(copyright_quoted);
}

void
playeripc_on_error_message(HXEmbeddedWindow* window,
                           gint rma_code,
                           gint user_code,
                           gchar* error,
                           gchar* user_string,
                           gchar* more_info_url)
{
    gchar *cmd;
    const gchar* name;

    // XXXRGG: hxclientkit doesn't give us the severity. Use 3 (General)
    gint severity = 3;

    name = hxembedded_window_get_name(window);

    gchar* user_string_quoted = g_shell_quote(user_string);
    gchar* more_info_url_quoted = g_shell_quote(more_info_url);
    gchar* error_quoted = g_shell_quote(error);

    cmd = g_strdup_printf("Callback %s OnErrorMessage %d,%d,%d,%s,%s,%s\n",
                          name, severity, rma_code, user_code,
                          user_string_quoted, more_info_url_quoted, error_quoted);
    
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(user_string_quoted);
    g_free(more_info_url_quoted);
    g_free(error_quoted);
}

void
playeripc_on_goto_url(HXEmbeddedWindow* window,
                        gchar* url,
                        gchar* target)
{
    gchar *cmd;
    const gchar* name;

    name = hxembedded_window_get_name(window);

    gchar* url_quoted = g_shell_quote(url);    
    gchar* target_quoted = g_shell_quote(target);    

    cmd = g_strdup_printf("Callback %s OnGotoURL %s,%s\n",
                          name, url_quoted, target_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(url_quoted);
    g_free(target_quoted);
}

void
playeripc_on_mute_change(HXEmbeddedWindow* window, gboolean mute)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);

    cmd = g_strdup_printf("Callback %s OnMuteChange %d\n",
                          name, mute);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
}

void
playeripc_on_play_state_change(HXEmbeddedWindow* window,
                               HXContentStateType old_state,
                               HXContentStateType new_state)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);

    cmd = g_strdup_printf("Callback %s OnPlayStateChange %d,%d\n",
                          name, (int)old_state, (int)new_state);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);    
}

void
playeripc_on_pos_length(HXEmbeddedWindow* window,
                        gint              pos,
                        gint              len)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnPosLength %d,%d\n",
                          name, pos, len);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);    
    
}

void
playeripc_on_position_change(HXEmbeddedWindow* window,
                             gint              pos,
                             gint              len)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnPositionChange %d,%d\n",
                          name, pos, len);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);            
}

void
playeripc_on_post_seek(HXEmbeddedWindow* window, gint old_time, gint new_time)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnPostSeek %d,%d\n",
                          name, old_time, new_time);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);    
}

void
playeripc_on_prefetch_complete(HXEmbeddedWindow* window,
                               HXBufferingReason /* reason */,
                               guint             percent_complete)
{
    gchar *cmd;
    const gchar* name;

    if(percent_complete == 100)
    {
        name = hxembedded_window_get_name(window);
    
        cmd = g_strdup_printf("Callback %s OnPrefetchComplete\n",
                              name);
        
        send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
        g_free(cmd);
    }
}

void
playeripc_on_pre_seek(HXEmbeddedWindow* window,
                     gint              old_time,
                     gint              new_time)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnPreSeek %d,%d\n",
                          name, old_time, new_time);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);    
}

void
playeripc_on_presentation_closed(HXEmbeddedWindow* window)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnPresentationClosed\n",
                          name);
    
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);        
}

void
playeripc_on_presentation_opened(HXEmbeddedWindow* window)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnPresentationOpened\n",
                          name);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);        
}

void 
playeripc_on_show_status(HXEmbeddedWindow* window,
                         const gchar*      status_text)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);

    gchar* status_text_quoted = g_shell_quote(status_text);    
    
    cmd = g_strdup_printf("Callback %s OnShowStatus %s\n",
                          name, status_text_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(status_text_quoted);    
}

void
playeripc_on_state_change(HXEmbeddedWindow* window,
                          HXContentStateType old_state,
                          HXContentStateType new_state)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnStateChange %d,%d\n",
                          name, (int)old_state, (int)new_state);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);    
}

void
playeripc_on_title_change(HXEmbeddedWindow* window,
                          const char*       /* unused */,
                          GValue*           title_value)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);

    const gchar* title = g_value_get_string(title_value);
    gchar* title_quoted = g_shell_quote(title);    
    
    cmd = g_strdup_printf("Callback %s OnTitleChange %s\n",
                          name, title_quoted);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
    g_free(title_quoted);        
}

void
playeripc_on_volume_change(HXEmbeddedWindow* window,
                           guint             new_volume)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    cmd = g_strdup_printf("Callback %s OnVolumeChange %d\n",
                          name, new_volume);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));
    
    g_free(cmd);
}

gboolean
playeripc_on_key_down(HXEmbeddedWindow* window, GdkEventKey* event)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);

    // XXXRGG: translate state and keyval to windows equivalents
    cmd = g_strdup_printf("Callback %s OnKeyDown %d,%d\n",
                          name, event->state, event->keyval);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);
    
    return FALSE; // propagate
}

gboolean
playeripc_on_key_press(HXEmbeddedWindow* window, GdkEventKey* event)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    // XXXRGG: translate state and keyval to windows equivalents
    cmd = g_strdup_printf("Callback %s OnKeyPress %d,%d\n",
                          name, event->state, event->keyval);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);

    return FALSE; // propagate
}

gboolean
playeripc_on_key_up(HXEmbeddedWindow* window, GdkEventKey* event)
{
    gchar *cmd;

    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    // XXXRGG: translate state and keyval to windows equivalents
    cmd = g_strdup_printf("Callback %s OnKeyUp %d,%d\n",
                          name, event->state, event->keyval);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);

    return FALSE; // propagate
}

gboolean
playeripc_on_lbutton_down(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 1)
    {
        gchar *cmd;

        const gchar* name;
        name = hxembedded_window_get_name(window);

        // XXXRGG: translate state and keyval to windows equivalents
        cmd = g_strdup_printf("Callback %s OnLButtonDown %d,%d,%d\n",
                              name, event->state, (gint)event->x, (gint)event->y);
        send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

        g_free(cmd);
    }
    return FALSE; // propagate
}

gboolean
playeripc_on_lbutton_up(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 1)
    {
        gchar *cmd;

        const gchar* name;
        name = hxembedded_window_get_name(window);

        // XXXRGG: translate state and keyval to windows equivalents
        cmd = g_strdup_printf("Callback %s OnLButtonUp %d,%d,%d\n",
                              name, event->state, (gint)event->x, (gint)event->y);
        send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

        g_free(cmd);
    }
    return FALSE; // propagate
}

gboolean
playeripc_on_mouse_move(HXEmbeddedWindow* window, GdkEventMotion* event)
{
    gchar *cmd;
    const gchar* name;
    name = hxembedded_window_get_name(window);
    
    // XXXRGG: translate state and keyval to windows equivalents
    cmd = g_strdup_printf("Callback %s OnMouseMove %d,%d,%d\n",
                          name, event->state, (gint)event->x, (gint)event->y);
    send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

    g_free(cmd);
    
    return FALSE; // propagate
}

gboolean
playeripc_on_rbutton_down(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 2)
    {
        gchar *cmd;
        const gchar* name;
        name = hxembedded_window_get_name(window);
    
        // XXXRGG: translate state and keyval to windows equivalents
        cmd = g_strdup_printf("Callback %s OnRButtonDown %d,%d,%d\n",
                              name, event->state, (gint)event->x, (gint)event->y);
        send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

        g_free(cmd);
    }

    return FALSE; // propagate
}

gboolean
playeripc_on_rbutton_up(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 2)
    {
        gchar *cmd;
    
        const gchar* name;
        name = hxembedded_window_get_name(window);
        
        // XXXRGG: translate state and keyval to windows equivalents
        cmd = g_strdup_printf("Callback %s OnRButtonUp %d,%d,%d\n",
                              name, event->state, (gint)event->x, (gint)event->y);
        send_ipc(g_playeripc.embedded_callbacks_channel, cmd, strlen(cmd));

        g_free(cmd);
    }

    return FALSE; // propagate
}
