/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: embeddedapp.cpp,v 1.39.2.16 2004/12/07 21:17:19 nhart 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 "appver.h"

#include "embeddedapp.h"
#include "commonapp.h"
#include "playeripc.h"
#include "hxbin.h"
#include "contextmenu.h"
#include "hxplayer-i18n.h"
#include "hxgvalue.h"
#include "hxgprefs.h"

#include "about.h"
#include "prefsdialog.h"
#include "statistics.h"

/* Status widgets */
#include "hxstatusfield.h"
#include "hxstatusinfopanel.h"
#include "hxstatuspositionfield.h"
#include "hxstatuspositionslider.h"
#include "hxstatustacctrl.h"
#include "hxstatuscongestion.h"

#include "embddef.h"

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#include <glib.h>

/* This file contains functions with several different prefixes.
 * They include:
 *   hxembedded_window - operate on HXEmbeddedWindow objects. A
 *                       HXEmbeddedWindow object represents a
 *                       single <embed> tag in an html page. It
 *                       could be an image window, button, status
 *                       bar, etc.
 *
 *   hxembedded_player - This is a collection of HXEmbeddedWindow
 *                       that, together, define a player.
 *
 *   hxembedded_console - This is a collection of players, and a
 *                        set of control "windows" that together
 *                        define a console.
 *
 *   hew - For (h)xplayer (e)mbedded (w)idget, these functions are
 *         designed to work as callbacks to the hx_player_widget.
 *
 *   no prefix - These functions are "private" supporting functions for
 *               one of the methods above, and are not intended for general
 *               use
 *
 */

/* Constants:
 * ==========
 */

#define FF_RW_UPDATE_PERIOD 100 // ms = 0.5 seconds

#define HX_CALLBACK_KEY_EVENTS ((HXCallbackFlags)             \
                                (HX_CALLBACK_ON_KEY_DOWN  |   \
                                 HX_CALLBACK_ON_KEY_PRESS |   \
                                 HX_CALLBACK_ON_KEY_UP))

#define HX_CALLBACK_MOUSE_EVENTS ((HXCallbackFlags)              \
                                  (HX_CALLBACK_ON_MOUSE_MOVE   | \
                                   HX_CALLBACK_ON_LBUTTON_UP   | \
                                   HX_CALLBACK_ON_LBUTTON_DOWN | \
                                   HX_CALLBACK_ON_RBUTTON_UP   | \
                                   HX_CALLBACK_ON_RBUTTON_DOWN))

#define HX_CALLBACK_ERROR_EVENTS ((HXCallbackFlags)              \
                                  (HX_CALLBACK_ON_ERROR_MESSAGE)) 
                                  

#define POS_LEN_CALLBACK_INTERVAL 1000 /* ms -- once a second */

/* Forward Declarations:
 * =====================
 */

extern "C"
{
/* Commands for the context menu (similar to hmw_* in hxplayer.h) */
void hew_play(GtkWidget* widget);
void hew_stop(GtkWidget* widget);
void hew_pause(GtkWidget* widget);
void hew_mute(GtkWidget* widget);
void hew_volume_changed(GtkWidget* widget);

gboolean hew_ff_start(HXEmbeddedWindow* window, GdkEventButton* event);
gboolean hew_ff(gpointer data);
gboolean hew_ff_stop(HXEmbeddedWindow* window, GdkEventButton* event);
gboolean hew_rewind_start(HXEmbeddedWindow* window, GdkEventButton* event);
gboolean hew_rewind(gpointer data);
gboolean hew_rewind_stop(HXEmbeddedWindow* window, GdkEventButton* event);

/* Callbacks for the embedded widget (similar to hpw in hxwindow.cpp) */
void hepw_mute_changed(HXEmbeddedWindow* window, gboolean mute);
void hepw_play_buffer_contact(HXEmbeddedWindow* window);
void hepw_pause(HXEmbeddedWindow* window);
void hepw_stop(HXEmbeddedWindow* window);
void hepw_volume_changed(HXEmbeddedWindow* window, guint vol);
void hepw_error(HXEmbeddedWindow* window, GError* error);
void hepw_hxerror(HXEmbeddedWindow* window, guint hxcode, guint user_code, const gchar* error_string, const gchar* user_string, const gchar* more_info_url);
void hepw_request_upgrade(HXEmbeddedWindow* window, const gchar* url, GList* components_list, gboolean blocking, gpointer);
void hepw_request_authentication(HXEmbeddedWindow* window, const gchar* server_proxy, const gchar* realm, gboolean is_proxy_server);
void hepw_open_window(HXEmbeddedWindow* window, gchar* url, gchar* target);
void hepw_goto_url(HXEmbeddedWindow* window, gchar* url, gchar* target);
void hepw_start_seeking(HXEmbeddedWindow* window);
void hepw_stop_seeking(HXEmbeddedWindow* window);
};

void     hxembedded_window_hookup_ipc_callbacks(HXEmbeddedWindow* window);
void     hxembedded_window_show_volume_popup   (HXEmbeddedWindow* window,
                                                GtkWidget* widget);
void     hxembedded_window_volume_changed      (HXEmbeddedWindow* window);


/* Data structures
 * ===============
 */

// Signal-based callbacks that are per-HXEmbeddedWindow
typedef struct
{
    gboolean is_enabled;
    gulong id;
} HXWindowSigInfo;

// Signal-based callbacks that are per-HXEmbeddedPlayer
typedef struct
{
    gboolean is_enabled;
    GObject* target;
    gulong id;
} HXPlayerSigInfo;

// Non-signal-based callbacks that are per-HXEmbeddedPlayer
typedef struct
{
    gboolean is_enabled;
} HXPlayerCallbackInfo;

struct _HXEmbeddedConsole;
typedef struct _HXEmbeddedConsole HXEmbeddedConsole;

typedef struct
{
    HXPlayer* player;
    HXBin* hxbin;

    HXContextMenu* context_menu;
} HXEmbeddedWindowImageWindow;

typedef struct
{
    GtkWidget* button;
    GtkWidget* image;

    GdkPixbuf* mute_pixbuf;
    GdkPixbuf* off_pixbuf;
    GdkPixbuf* low_pixbuf;
    GdkPixbuf* mid_pixbuf;
    GdkPixbuf* high_pixbuf;

} HXEmbeddedWindowMuteCtrl;

typedef struct
{
    GtkWidget* scale;
} HXEmbeddedWindowVolumeSlider;

typedef struct
{
    GtkWidget* button;
    GtkWidget* image;
    GdkPixbuf* play_pixbuf;
    GdkPixbuf* pause_pixbuf;
} HXEmbeddedWindowPlayPauseButton;

typedef struct
{
    GtkWidget* button;
} HXEmbeddedWindowPlayButton;

typedef struct
{
    GtkWidget* button;
} HXEmbeddedWindowPauseButton;

typedef struct
{
    GtkWidget* button;
} HXEmbeddedWindowStopButton;

typedef struct
{
    GtkWidget* button;
    gint timer_id;
} HXEmbeddedWindowFastForwardButton;

typedef struct
{
    GtkWidget* button;
    gint timer_id;
} HXEmbeddedWindowRewindButton;

typedef struct
{
    GtkWidget* popup_window;
    GtkWidget* button;
    GtkWidget* image;

    GdkPixbuf* mute_pixbuf;
    GdkPixbuf* off_pixbuf;
    GdkPixbuf* low_pixbuf;
    GdkPixbuf* mid_pixbuf;
    GdkPixbuf* high_pixbuf;

} HXEmbeddedWindowMuteVolumePopup;

typedef struct
{
    guint stream_id;
    HXDataStream* stream;
    gchar* url;
    gchar* mime_type;
} HXDataStreamInfo;


typedef struct _HXEmbeddedWindow
{
    guint id;
    GList* streams_list;
    gchar* src_url;     /* Qualified url of the <embed> tag, passed by NewStream  */ 
    HXEmbeddedWindowAttributes attr;    
    gboolean is_global; /* Affects all players in the console */
    GtkWidget* window;
    HXEmbeddedConsole* console;
    XID parent_xid;
    guint parent_socket_id;
    GtkWidget* widget;
    gboolean is_window_set;
    GtkWidget* alignment;

    /* Helix error information, for use with the JavaScript api */
    guint helix_error_code;
    guint helix_user_code;
    gchar* helix_error_string;
    gchar* helix_user_string;
    gchar* helix_more_info_url;
    
    /* Attributes that can be set via javascript */
    gchar* author_override;
    gchar* title_override;
    gchar* copyright_override;

    /* Last status message sent by the OnShowStatus callback. This is returned by
       GetLastStatus */
    gchar* status_message; 

    /* Show status information in the image window. Set using SetImageStatus. */
    // XXXRGG: Implement this
    gboolean show_status_in_image_window;

    /* Enable/disable the context menu */
    gboolean enable_context_menu;

    /* XXXRGG: Not sure what this is (does it disable error messagebox's?)
       but you can get/set it with (Set)EnableMessageBox */
    gboolean enable_message_box;

    /* Determines whether errors go to the error callbacks, if the player
       should pop up a dialog */
    gboolean enable_error_events;
    
    /* Various other enables/disables */
    gboolean enable_original_size;
    gboolean enable_double_size; //XXXRGG What is DoubleSize in the context of an embedded player?
    gboolean enable_fullscreen;
    gboolean enable_seek;
        
    /* attr.controls is used to determine the type */
    HXEmbeddedWindowImageWindow* image_window;
    HXEmbeddedWindowMuteCtrl* mute_ctrl;
    HXEmbeddedWindowVolumeSlider* volume_slider;
    HXEmbeddedWindowPlayButton* play_button;
    HXEmbeddedWindowPauseButton* pause_button;
    HXEmbeddedWindowStopButton* stop_button;
    HXEmbeddedWindowPlayPauseButton* play_pause_button;
    HXEmbeddedWindowFastForwardButton* ff_button;
    HXEmbeddedWindowRewindButton* rewind_button;
    HXEmbeddedWindowMuteVolumePopup* mute_volume_popup;
    
    HXStatusDisplayPositionSlider* position_slider;
    HXStatusDisplayInfoPanel* info_panel;
    HXStatusDisplayPositionField* position_field;
    HXStatusDisplayStatusField* status_field;
    HXStatusDisplayTACCtrl* tac_ctrl;
    HXStatusDisplayCongestion* congestion;

    GtkWidget* unknown;
    
    /* timer for driving the OnPosLength and OnPositionChanged */
    gboolean pos_len_callback_timer_is_running; 
    
    /* Signal handler info */
    struct
    {
        HXPlayerCallbackInfo on_clip_closed;
        HXPlayerCallbackInfo on_clip_opened;
        HXPlayerCallbackInfo on_pos_length;
        HXPlayerCallbackInfo on_position_change;
        HXPlayerCallbackInfo on_post_seek;
        HXPlayerCallbackInfo on_pre_seek;
        HXPlayerCallbackInfo on_presentation_closed;
        HXPlayerCallbackInfo on_presentation_opened;

        HXPlayerSigInfo on_title_change;
        HXPlayerSigInfo on_author_change;
        HXPlayerSigInfo on_copyright_change;
        HXPlayerSigInfo on_prefetch_complete;
        HXPlayerSigInfo on_show_status;        
        HXPlayerSigInfo on_buffering;
        HXPlayerSigInfo on_contacting;
        HXPlayerSigInfo on_error_message;
        HXPlayerSigInfo on_goto_url;
        HXPlayerSigInfo on_mute_change;
        HXPlayerSigInfo on_play_state_change;
        HXPlayerSigInfo on_state_change;
        HXPlayerSigInfo on_volume_change;
    } player_handlers;

    struct
    {
        HXWindowSigInfo on_key_down;
        HXWindowSigInfo on_key_press;
        HXWindowSigInfo on_key_up;
        HXWindowSigInfo on_lbutton_down;
        HXWindowSigInfo on_lbutton_up;
        HXWindowSigInfo on_mouse_move;
        HXWindowSigInfo on_rbutton_down;
        HXWindowSigInfo on_rbutton_up;
    } window_handlers;

    /* Dialogs that can be opened by the embedded player */
    GtkDialog* about_dialog;
    GtkDialog* preferences_dialog;
    GtkDialog* statistics_dialog;

    gboolean is_seeking;

    HXStatisticsObserver* title_observer;
    HXStatisticsObserver* author_observer;
    HXStatisticsObserver* copyright_observer;
} HXEmbeddedWindow;

typedef struct _HXEmbeddedConsole
{
    /* The audio player is created and used if the console ends up having to
       play, but does not have an ImageWindow to play into. The image window
       player links to an ImageWindow in the console, if available. */
    HXPlayer* audio_player;
    HXPlayer* image_window_player;

    GList* windows_list;
    const gchar* name;
    
} HXEmbeddedConsole;

typedef struct _HXEmbeddedApp
{
    /* Embedded */
    GList* consoles_list;

    HXEmbeddedConsole* master_console;
    HXEmbeddedConsole* active_console;
    
    /* private - modified by hxwindow_add_update_timer */
    guint update_timer_id;
    guint update_timer_ref_count;

    guint next_window_id;

    gboolean have_callbacks;

    gboolean show_unsupported_browser_dialog;
    gboolean unsupported_browser_dialog_shown;
    
} HXEmbeddedApp;

typedef struct _HXUnsupportedBrowserDialog
{
    GladeXML* xml;
    HXEmbeddedWindow* window;
    GtkWidget* show_warning_in_future_checkbox;
} HXUnsupportedBrowserDialog;


static GtkWidget* create_controls_image_window         (HXEmbeddedWindow* window);
static void       popup_volume_update_icons            (HXEmbeddedWindow* window);
static void       hxembedded_window_update_volume_icons(HXEmbeddedWindow* window);

/* hxembedded_window_get_player may create a player if none exists */
static HXPlayer* hxembedded_window_get_player          (HXEmbeddedWindow* window);
static HXPlayer* hxembedded_window_get_player_no_create(HXEmbeddedWindow* window);


/* Our global data is stored in an app object.
   Data that is common between our player and the top-level
   player can be found in g_hxcommon_app. */
static HXEmbeddedApp g_hxembedded_app;

static void
hxwindow_update_mute_ctrl_icons(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player_no_create(window);

    gboolean mute = FALSE;
    guint vol = 0;

    if(player)
    {
        mute = hx_player_is_muted(HX_PLAYER(player));
        vol = hx_player_get_volume(HX_PLAYER(player));
    }
    
    if(mute)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_ctrl->image),
                                  window->mute_ctrl->mute_pixbuf);
    }
    else if(vol == 0)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_ctrl->image),
                                  window->mute_ctrl->off_pixbuf);
    }
    else if(vol < 33)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_ctrl->image),
                                  window->mute_ctrl->low_pixbuf);
    }
    else if(vol < 66)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_ctrl->image),
                                  window->mute_ctrl->mid_pixbuf);
    }
    else
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_ctrl->image),
                                  window->mute_ctrl->high_pixbuf);
    }                  
}

/* hxembedded_console implementation
 * =================================
 */
static void
hxembedded_console_destroy_player_window_associations(HXEmbeddedConsole* console)
{
    /* Destroys all players, and all window associations
       for a given console */
    GList* windows_iter;
    HXEmbeddedWindow* window = NULL;
    HXCallbackFlags flags;
    
    windows_iter = console->windows_list;
    while(windows_iter)
    {
        window = (HXEmbeddedWindow*)windows_iter->data;

        /* Remove all player signals */
        flags = window->attr.scriptcallbacks;
        window->attr.scriptcallbacks = (HXCallbackFlags)
            (window->attr.scriptcallbacks &
                 ~(HX_CALLBACK_ON_AUTHOR_CHANGE       |
                   HX_CALLBACK_ON_BUFFERING           |
                   HX_CALLBACK_ON_CLIP_CLOSED         |
                   HX_CALLBACK_ON_CLIP_OPENED         |
                   HX_CALLBACK_ON_CONTACTING          |
                   HX_CALLBACK_ON_COPYRIGHT_CHANGE    |
                   HX_CALLBACK_ON_ERROR_MESSAGE       |
                   HX_CALLBACK_ON_GOTO_URL            |
                   HX_CALLBACK_ON_MUTE_CHANGE         |
                   HX_CALLBACK_ON_PLAY_STATE_CHANGE   |
                   HX_CALLBACK_ON_POS_LENGTH          |
                   HX_CALLBACK_ON_POSITION_CHANGE     |
                   HX_CALLBACK_ON_POST_SEEK           |
                   HX_CALLBACK_ON_PREFETCH_COMPLETE   |
                   HX_CALLBACK_ON_PRE_SEEK            |
                   HX_CALLBACK_ON_PRESENTATION_CLOSED |
                   HX_CALLBACK_ON_PRESENTATION_OPENED |
                   HX_CALLBACK_ON_SHOW_STATUS         |
                   HX_CALLBACK_ON_STATE_CHANGE        |
                   HX_CALLBACK_ON_TITLE_CHANGE        |
                   HX_CALLBACK_ON_VOLUME_CHANGE));
        
        hxembedded_window_hookup_ipc_callbacks(window);
        window->attr.scriptcallbacks = flags;
        
        windows_iter = g_list_next(windows_iter);
    }

    /* The corresponding ImageWindow may have been removed from this console.
       Set to NULL, and let the console building function reset it. */
    console->image_window_player = NULL;
}

void
hxembedded_console_adopt_player(HXEmbeddedConsole* console)
{
    GList* windows_iter;
    HXEmbeddedWindow* window = NULL;
    HXPlayer* player;
    
    /* Go through all the status widgets, set the player for them */
    windows_iter = console->windows_list;
    while(windows_iter)
    {
        window = (HXEmbeddedWindow*)windows_iter->data;
        player = hxembedded_window_get_player(window);

        if(player)
        {                
            /* Create observers */
            if(window->title_observer)
            {
                g_object_unref(window->title_observer);
            }
            window->title_observer = hx_statistics_observer_new(player, "title");
        
            if(window->author_observer)
            {
                g_object_unref(window->author_observer);
            }
            window->author_observer = hx_statistics_observer_new(player, "author");

            if(window->copyright_observer)
            {
                g_object_unref(window->copyright_observer);
            }
            window->copyright_observer = hx_statistics_observer_new(player, "copyright");
        }
        
        player = hxembedded_window_get_player_no_create(window);
        if(player)
        {
            if(window->info_panel)
            {
                hxstatus_display_set_player(HX_STATUS_DISPLAY(window->info_panel),
                                            player);
            }
            if(window->position_field)
            {
                hxstatus_display_set_player(HX_STATUS_DISPLAY(window->position_field),
                                            player);
            }
            if(window->status_field)
            {
                hxstatus_display_set_player(HX_STATUS_DISPLAY(window->status_field),
                                            player);
            }
            if(window->position_slider)
            {
                hxstatus_display_set_player(HX_STATUS_DISPLAY(window->position_slider),
                                            player);
            }
            if(window->tac_ctrl)
            {
                hxstatus_display_set_player(HX_STATUS_DISPLAY(window->tac_ctrl),
                                            player);
            }
            if(window->congestion)
            {
                hxstatus_display_set_player(HX_STATUS_DISPLAY(window->congestion),
                                            player);
            }
            if(window->volume_slider)
            {
                guint vol;
                vol = hx_player_get_volume(player);
                gtk_range_set_value(GTK_RANGE(window->volume_slider->scale), (gdouble) vol);                    
            }
            if(window->mute_ctrl)
            {
                gboolean muted;
                muted = hx_player_is_muted(player);
                    
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(window->mute_ctrl->button),
                                             muted);

                hxembedded_window_update_volume_icons (window);
            }
            if(window->mute_volume_popup)
            {
                popup_volume_update_icons(window);
            }
        }

        windows_iter = g_list_next(windows_iter);
    }        

    /* Update ipc callbacks */
    hxembedded_window_hookup_ipc_callbacks(window);

}

/* This function assumes there will be only one player in the console.
   This is not necessarily true for a _master console. */
gboolean
hxembedded_console_build_players_from_windows(HXEmbeddedConsole* console)
{
    GList* windows_iter;
    HXEmbeddedWindow* window = NULL;

    /* Step 0: See if all the windows created with Embed have been set
       with SetWindow. If not, we probably have more SetWindow commands
       pending, so we return FALSE for now, and wait until all outstanding
       windows are accounted for. */
    windows_iter = console->windows_list;
    while(windows_iter)
    {
        window = (HXEmbeddedWindow*)windows_iter->data;

        if(!window->is_window_set &&
           (window->attr.width > 0 && window->attr.height > 0))
        {
            return FALSE;
        }
            
        windows_iter = g_list_next(windows_iter);
    }

    /* Step 1: Break all existing player-console-window associations */
    hxembedded_console_destroy_player_window_associations(console);

    /* Step 2: See if we can find an image_window player for this console */
    windows_iter = console->windows_list;
    while(windows_iter)
    {
        window = (HXEmbeddedWindow*)windows_iter->data;
        
        if(window->image_window)
        {
            if(console->image_window_player)
            {
                /* We already have a player_widget - this can happen if
                   we're using a recent version of mozilla, and we transition
                   between two pages with plugins, and those pages both have
                   consoles with the same name (mozilla will SetWindow the new
                   plugins before UnsetWindow'ing the old plugins), or if an
                   author has put two ImageWindows in a webpage. Content will play
                   to the first ImageWindow it comes across.

                   In addition, there are a couple of other cases where there can be
                   multiple ImageWindows per console.

                   1. Wall of TV's -- multiple image windows all playing the same clip
                   2. SMIL Regions -- multiple image windows playing different regions
                      of the same smil file.
                */
            }
            else
            {
                console->image_window_player = window->image_window->player;
            }
        }
    
        windows_iter = g_list_next(windows_iter);
    }
    
    if(console->image_window_player)
    {
        hxembedded_console_adopt_player(console);
    }

    return TRUE;
}

/* Get a list of all windows in this console, and potentially in other
   consoles if we're the master console. */
GList*
hxembedded_console_get_windows_list(HXEmbeddedConsole* console)
{
    GList* windows_list = NULL;
    GList* windows_list_iter;
    HXEmbeddedConsole* consoles[2] = { NULL, NULL };
    guint i;

    consoles[0] = console;

    if(console &&
       console == g_hxembedded_app.master_console)
    {
        consoles[1] = g_hxembedded_app.active_console;
    }

    for(i = 0; i < sizeof(consoles) / sizeof(*consoles); i++)
    {
        if(consoles[i])
        {
            windows_list_iter = console->windows_list;
            while(windows_list_iter)
            {
                windows_list = g_list_append(windows_list, windows_list_iter->data);
                windows_list_iter = g_list_next(windows_list_iter);
            }
        }
    }

    return windows_list;
}

/* hxembedded_window implementation
 * =================================
 */
static HXEmbeddedWindow*
hxembedded_window_get_window(GtkWidget* widget)
{
    GtkWidget* toplevel;
    HXEmbeddedWindow* window;
           
    toplevel = hxcommon_get_toplevel_widget_no_menus(widget);
    window = (HXEmbeddedWindow*) g_object_get_data(G_OBJECT(toplevel), "hxembeddedwindow");
    g_assert(window != NULL);

    return window;
}

static HXPlayer*
hxembedded_window_get_player_no_create(HXEmbeddedWindow* window)
{
    HXPlayer* player = NULL;
    
    g_return_val_if_fail(window->console, NULL);

    /* Try the master console first */
    if(g_hxembedded_app.master_console)
    {
        if(g_hxembedded_app.master_console->image_window_player)
        {
            player = g_hxembedded_app.master_console->image_window_player;
        }
        else if(g_hxembedded_app.master_console->audio_player)
        {
            player = g_hxembedded_app.master_console->audio_player;
        }
    }

    if(!player)
    {
        player = window->console->image_window_player;
    }

    if(!player)
    {
        player = window->console->audio_player;
    }
    
    return player;
    
}

static gint
hxembedded_window_popup_menu(HXEmbeddedWindow* window,
                              GdkEvent* event,
                              GtkWidget* /* widget */)
{
    GdkEventButton *event_button;

    if(!window->enable_context_menu)
    {
        return FALSE;
    }
        
    if (event->type == GDK_BUTTON_PRESS)
    {
        event_button = (GdkEventButton *) event;
        if (event_button->button == 3)
	{
            g_assert(window->image_window != NULL);
            hx_context_menu_popup(window->image_window->context_menu, &event->button);
            return TRUE;
	}
    }
    
    return FALSE; // propagate
}

static HXPlayer*
hxembedded_window_get_player(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player_no_create(window);

    if(!player)
    {
        GtkWidget* widget = hx_player_new();
        player = HX_PLAYER(widget);

        g_signal_connect_swapped(G_OBJECT(player),
                                 "button_press_event",
                                 G_CALLBACK(hxembedded_window_popup_menu),
                                 window);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "hxerror",
                                 G_CALLBACK(hepw_hxerror),
                                 window);
    
        g_signal_connect_swapped(G_OBJECT(player),
                                 "request_upgrade",
                                 G_CALLBACK(hepw_request_upgrade),
                                 window);

        g_signal_connect_swapped(G_OBJECT(player),
                                 "request_authentication",
                                 G_CALLBACK(hepw_request_authentication),
                                 window);

        g_signal_connect_swapped(G_OBJECT(player),
                                 "has_feature",
                                 G_CALLBACK(hxcommon_player_has_feature),
                                 player);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "play",
                                 G_CALLBACK(hepw_play_buffer_contact),
                                 window);
    
        g_signal_connect_swapped(G_OBJECT(player),
                                 "buffering",
                                 G_CALLBACK(hepw_play_buffer_contact),
                                 window);

        g_signal_connect_swapped(G_OBJECT(player),
                                 "contacting",
                                 G_CALLBACK(hepw_play_buffer_contact),
                                 window);
    
        g_signal_connect_swapped(G_OBJECT(player),
                                 "pause",
                                 G_CALLBACK(hepw_pause),
                                 window);

        g_signal_connect_swapped(G_OBJECT(player),
                                 "stop",
                                 G_CALLBACK(hepw_stop),
                                 window);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "mute_changed",
                                 G_CALLBACK(hepw_mute_changed),
                                 window);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "volume_changed",
                                 G_CALLBACK(hepw_volume_changed),
                                 window);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "goto_url",
                                 G_CALLBACK(hepw_goto_url),
                                 window);    
    
        g_signal_connect_swapped(G_OBJECT(player),
                                 "open_window",
                                 G_CALLBACK(hepw_open_window),
                                 window);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "start_seeking",
                                 G_CALLBACK(hepw_start_seeking),
                                 window);    

        g_signal_connect_swapped(G_OBJECT(player),
                                 "stop_seeking",
                                 G_CALLBACK(hepw_stop_seeking),
                                 window);    

        hxcommon_show_status_in_player_widget(HX_PLAYER(player));

        if(window->image_window)
        {
            window->image_window->player = player;
            window->console->image_window_player = player;
        }
        else
        {
            window->console->audio_player = player;
        }

        hxembedded_console_adopt_player(window->console);
    }

    return player;
}

HXContentStateType
hxembedded_window_get_content_state(HXEmbeddedWindow* window)
{
    HXPlayer* player;
            
    player = hxembedded_window_get_player(window);
    g_return_val_if_fail(player != NULL, HX_CONTENT_STATE_NOT_LOADED);
        
    return hx_player_get_content_state (HX_PLAYER(player));
}

void
hxembedded_window_unset_console(HXEmbeddedWindow* window)
{
    if(!window->console)
    {
        /* No console to unset */
        return;
    }
    
    /* This window currently has a console set. Remove it... */
    window->console->windows_list = 
        g_list_remove(window->console->windows_list, window);
    if(g_list_length(window->console->windows_list) == 0)
    {
        /* Remove this console */
        g_hxembedded_app.consoles_list = 
            g_list_remove(g_hxembedded_app.consoles_list, window->console);
        if(strcasecmp(window->console->name, "_master") == 0)
        {
            g_hxembedded_app.master_console = NULL;
        }
        g_free(window->console);

        if(g_list_length(g_hxembedded_app.consoles_list) == 0)
        {
            /* XXXRGG: We might want to quit here. The mozilla plugin should be
               able to restart the embedded player when it needs a console again.
               Requires some thought & testing, though, so keep running for now. */
            
            printf("All consoles have been closed\n");
        }
    }
    else 
    {
        /* Rebuild console */
        hxembedded_console_build_players_from_windows(window->console);
    }
    window->console = NULL;    
}

void
hxembedded_window_set_console(HXEmbeddedWindow* window, const gchar* console_name)
{
    GList* iter = NULL;
    HXEmbeddedConsole* console = NULL;

    if(window->attr.console)
    {
        if(window->console && strcmp(window->attr.console, console_name) == 0)
        {
            /* Console name is the same -- no change needed */
            return;
        }
    }
    
    if(window->console)
    {
        hxembedded_window_unset_console(window);
    }

    if(window->attr.console != console_name)
    {
        if(window->attr.console)
        {
            g_free(window->attr.console);
        }
        window->attr.console = g_strdup(console_name);
    }
    
    if(strcasecmp(window->attr.console, "_unique") != 0)
    {
        /* See if there's already a console with the same name */
        iter = g_hxembedded_app.consoles_list;
        while(iter)
        {
            console = (HXEmbeddedConsole*)iter->data;
            if(strcmp(window->attr.console, console->name) == 0)
            {
                break;
            }
            iter = g_list_next(iter);
        }
    }

    if(!iter)
    {
        /* No existing console with this name. Create a new one. */
        console = g_new0(HXEmbeddedConsole, 1);
        g_hxembedded_app.consoles_list =
            g_list_append(g_hxembedded_app.consoles_list, console);

        console->name = g_strdup(console_name);

        if(strcasecmp(window->attr.console, "_master") == 0)
        {
            g_hxembedded_app.master_console = console;
        }
    }

    window->console = console;
    
    console->windows_list = 
        g_list_append(console->windows_list, window);
        
    hxembedded_console_build_players_from_windows(window->console);
}

gint
hxembedded_window_attach(HXEmbeddedWindowAttributes* attr)
{
    HXEmbeddedWindow* window = NULL;
    
    window = g_new0(HXEmbeddedWindow, 1);

    /* Initialize */
    window->enable_original_size = TRUE;
    window->enable_context_menu = TRUE;
    window->enable_error_events = FALSE; // Pop up error dialogs by default
    window->enable_seek = TRUE;

    /* We're taking ownership of all strings allocated within the attr struct */
    memcpy(&window->attr, attr, sizeof(*attr));
    
    window->id = g_hxembedded_app.next_window_id;
    g_hxembedded_app.next_window_id++;

    hxembedded_window_set_console(window, attr->console);
    
    /* Some pages embed an audio player by using an ImageWindow with
       height 0. We won't get a SetWindow for this window (in Mozilla,
       anyway), so handle creating this player here. */

    if((window->attr.controls & HX_CONTROLS_IMAGE_WINDOW) &&
       (window->attr.height == 0 || window->attr.width == 0))
    {
        /* Audio-only player */
        window->is_window_set = TRUE;
        create_controls_image_window(window);
    }
    
    return window->id;
}

void
hxembedded_window_add_ipc_callbacks(HXEmbeddedWindow* window, HXCallbackFlags flags)
{
    if(window->attr.name != NULL)
    {
        window->attr.scriptcallbacks = (HXCallbackFlags)(window->attr.scriptcallbacks | flags);
        hxembedded_window_hookup_ipc_callbacks(window);
    }
}

void
hxembedded_window_remove_ipc_callbacks(HXEmbeddedWindow* window, HXCallbackFlags flags)
{
    window->attr.scriptcallbacks = (HXCallbackFlags)(window->attr.scriptcallbacks & ~flags);
    hxembedded_window_hookup_ipc_callbacks(window);
}


static gboolean
hxembedded_window_call_pos_len_callbacks(gpointer data)
{
    guint pos;
    guint len;
    HXContentStateType state;
    
    HXEmbeddedWindow* window = (HXEmbeddedWindow*)data;
    HXPlayer* player = hxembedded_window_get_player(window);
    
    /* See if this callback is still active */
    if(!(window->attr.scriptcallbacks & HX_CALLBACK_ON_POS_LENGTH))
    {
        window->player_handlers.on_pos_length.is_enabled = FALSE;
    }

    if(!(window->attr.scriptcallbacks & HX_CALLBACK_ON_POSITION_CHANGE))
    {
        window->player_handlers.on_position_change.is_enabled = FALSE;
    }

    if(!(window->player_handlers.on_position_change.is_enabled ||
         window->player_handlers.on_pos_length.is_enabled))
    {
        /* no timer required */
        return FALSE;
    }

    state = hx_player_get_content_state(player);
    if(state != HX_CONTENT_STATE_PLAYING)
    {
        /* no timer required */
        return FALSE;
    }
    
    /* call callbacks */
    pos = hx_player_get_position(player);
    len = hx_player_get_length(player);

    if(window->player_handlers.on_pos_length.is_enabled)
    {
        playeripc_on_pos_length(window, pos, len);
    }
    if(window->player_handlers.on_position_change.is_enabled)
    {
        playeripc_on_position_change(window, pos, len);
    }

    /* Keep timer callback */
    return TRUE;
}

static void
hookup_window_ipc_callbacks(HXEmbeddedWindow* window)
{
    struct
    {
        guint mask;
        gchar* name;
        GCallback callback;
        HXWindowSigInfo* siginfo;
    } window_signal_map[8];

    guint i;

    /* window signals */
    window_signal_map[0].mask     = HX_CALLBACK_ON_KEY_DOWN;
    window_signal_map[0].name     = "key_press_event";
    window_signal_map[0].callback = G_CALLBACK(playeripc_on_key_down);
    window_signal_map[0].siginfo  = &window->window_handlers.on_key_down;

    window_signal_map[1].mask     = HX_CALLBACK_ON_KEY_PRESS;
    window_signal_map[1].name     = "key_press_event";
    window_signal_map[1].callback = G_CALLBACK(playeripc_on_key_press);
    window_signal_map[1].siginfo  = &window->window_handlers.on_key_press;

    window_signal_map[2].mask     = HX_CALLBACK_ON_KEY_UP;
    window_signal_map[2].name     = "key_release_event";
    window_signal_map[2].callback = G_CALLBACK(playeripc_on_key_up);
    window_signal_map[2].siginfo  = &window->window_handlers.on_key_up;

    window_signal_map[3].mask     = HX_CALLBACK_ON_LBUTTON_DOWN;
    window_signal_map[3].name     = "button_press_event";
    window_signal_map[3].callback = G_CALLBACK(playeripc_on_lbutton_down);
    window_signal_map[3].siginfo  = &window->window_handlers.on_lbutton_down;
    
    window_signal_map[4].mask     = HX_CALLBACK_ON_LBUTTON_UP;
    window_signal_map[4].name     = "button_release_event";
    window_signal_map[4].callback = G_CALLBACK(playeripc_on_lbutton_up);
    window_signal_map[4].siginfo  = &window->window_handlers.on_lbutton_up;

    window_signal_map[5].mask     = HX_CALLBACK_ON_MOUSE_MOVE;
    window_signal_map[5].name     = "motion_notify_event";
    window_signal_map[5].callback = G_CALLBACK(playeripc_on_mouse_move);
    window_signal_map[5].siginfo  = &window->window_handlers.on_mouse_move;
    
    window_signal_map[6].mask     = HX_CALLBACK_ON_RBUTTON_DOWN;
    window_signal_map[6].name     = "button_press_event";
    window_signal_map[6].callback = G_CALLBACK(playeripc_on_rbutton_down);
    window_signal_map[6].siginfo  = &window->window_handlers.on_rbutton_down;
    
    window_signal_map[7].mask     = HX_CALLBACK_ON_RBUTTON_UP;
    window_signal_map[7].name     = "button_release_event";
    window_signal_map[7].callback = G_CALLBACK(playeripc_on_rbutton_up);
    window_signal_map[7].siginfo  = &window->window_handlers.on_rbutton_up;

    g_assert((sizeof(window_signal_map) / sizeof(*window_signal_map)) == 8);

    for(i = 0; i < sizeof(window_signal_map) / sizeof(*window_signal_map); i++)
    {
        if((window_signal_map[i].mask & window->attr.scriptcallbacks) &&
           !window_signal_map[i].siginfo->is_enabled && window->window)
        {
            /* Add callback */
            if(window_signal_map[0].callback)
            {
                if(!window_signal_map[i].siginfo->is_enabled)
                {
                    window_signal_map[i].siginfo->id =
                        g_signal_connect_swapped(G_OBJECT(window->window),
                                                 window_signal_map[i].name,
                                                 G_CALLBACK(window_signal_map[i].callback),
                                                 window);
                    
                    window_signal_map[i].siginfo->is_enabled = TRUE;
                }
            }
            else
            {
                g_warning("Unimplemented callback requested! (mask=0x%08x)\n",
                          window_signal_map[i].mask);
            }
        }

        if(!(window_signal_map[i].mask & window->attr.scriptcallbacks) &&
           window_signal_map[i].siginfo->is_enabled)
        {
            /* Remove callback */
            if(window->window)
            {
                g_signal_handler_disconnect(G_OBJECT(window->window),
                                            window_signal_map[i].siginfo->id);
            }
            
            window_signal_map[i].siginfo->id = 0;
            window_signal_map[i].siginfo->is_enabled = FALSE;
        }
    }    
}

static void
hookup_player_ipc_callbacks(HXEmbeddedWindow* window, HXPlayer* player)
{
    struct
    {
        guint mask;
        HXPlayerCallbackInfo* cbinfo;
    } player_callback_map[6];

    struct
    {
        guint mask;
        gchar* name;
        GCallback callback;
        GObject* target;
        HXPlayerSigInfo* siginfo;
    } player_signal_map[13];    

    /* player signals */    
    guint i;
    HXPlayerSigInfo* siginfo;

    GObject* player_target = NULL;
    GObject* title_observer_target = NULL;
    GObject* author_observer_target = NULL;
    GObject* copyright_observer_target = NULL;    

    g_return_if_fail(player);
    g_return_if_fail(window->title_observer);
    g_return_if_fail(window->author_observer);
    g_return_if_fail(window->copyright_observer);
    
    player_target             = G_OBJECT(player);
    title_observer_target     = G_OBJECT(window->title_observer);
    author_observer_target    = G_OBJECT(window->author_observer);
    copyright_observer_target = G_OBJECT(window->copyright_observer);
    
    player_signal_map[0].mask = HX_CALLBACK_ON_BUFFERING;
    player_signal_map[0].name = "buffering";
    player_signal_map[0].callback = G_CALLBACK(playeripc_on_buffering);
    player_signal_map[0].target = player_target;
    player_signal_map[0].siginfo = &window->player_handlers.on_buffering;

    player_signal_map[1].mask = HX_CALLBACK_ON_CONTACTING;
    player_signal_map[1].name = "contacting";
    player_signal_map[1].callback = G_CALLBACK(playeripc_on_contacting);
    player_signal_map[1].target = player_target;
    player_signal_map[1].siginfo = &window->player_handlers.on_contacting;

    player_signal_map[2].mask = HX_CALLBACK_ON_ERROR_MESSAGE;
    player_signal_map[2].name = "hxerror";
    player_signal_map[2].callback = G_CALLBACK(playeripc_on_error_message);
    player_signal_map[2].target = player_target;
    player_signal_map[2].siginfo = &window->player_handlers.on_error_message;

    player_signal_map[3].mask = HX_CALLBACK_ON_GOTO_URL;
    player_signal_map[3].name = "goto_url";
    player_signal_map[3].callback = G_CALLBACK(playeripc_on_goto_url);
    player_signal_map[3].target = player_target;
    player_signal_map[3].siginfo = &window->player_handlers.on_goto_url;

    player_signal_map[4].mask = HX_CALLBACK_ON_MUTE_CHANGE;
    player_signal_map[4].name = "mute_changed";
    player_signal_map[4].callback = G_CALLBACK(playeripc_on_mute_change);
    player_signal_map[4].target = player_target;
    player_signal_map[4].siginfo = &window->player_handlers.on_mute_change;

    player_signal_map[5].mask = HX_CALLBACK_ON_PLAY_STATE_CHANGE;
    player_signal_map[5].name = "content_state_changed";
    player_signal_map[5].callback = G_CALLBACK(playeripc_on_play_state_change);
    player_signal_map[5].target = player_target;
    player_signal_map[5].siginfo = &window->player_handlers.on_play_state_change;

    player_signal_map[6].mask = HX_CALLBACK_ON_STATE_CHANGE;
    player_signal_map[6].name = "content_state_changed";
    player_signal_map[6].callback = G_CALLBACK(playeripc_on_state_change);
    player_signal_map[6].target = player_target;
    player_signal_map[6].siginfo = &window->player_handlers.on_state_change;
        
    player_signal_map[7].mask = HX_CALLBACK_ON_VOLUME_CHANGE;
    player_signal_map[7].name = "volume_changed";
    player_signal_map[7].callback = G_CALLBACK(playeripc_on_volume_change);
    player_signal_map[7].target = player_target;
    player_signal_map[7].siginfo = &window->player_handlers.on_volume_change;

    player_signal_map[8].mask = HX_CALLBACK_ON_SHOW_STATUS;
    player_signal_map[8].name = "status_changed";
    player_signal_map[8].callback = G_CALLBACK(playeripc_on_show_status);
    player_signal_map[8].target = player_target;
    player_signal_map[8].siginfo = &window->player_handlers.on_show_status;

    player_signal_map[9].mask = HX_CALLBACK_ON_PREFETCH_COMPLETE;
    player_signal_map[9].name = "buffering";
    player_signal_map[9].callback = G_CALLBACK(playeripc_on_prefetch_complete);
    player_signal_map[9].target = player_target;
    player_signal_map[9].siginfo = &window->player_handlers.on_prefetch_complete;

    player_signal_map[10].mask = HX_CALLBACK_ON_TITLE_CHANGE;
    player_signal_map[10].name = "statistic_modified";
    player_signal_map[10].callback = G_CALLBACK(playeripc_on_title_change);
    player_signal_map[10].target = title_observer_target;
    player_signal_map[10].siginfo = &window->player_handlers.on_title_change;

    player_signal_map[11].mask = HX_CALLBACK_ON_AUTHOR_CHANGE;
    player_signal_map[11].name = "statistic_modified";
    player_signal_map[11].callback = G_CALLBACK(playeripc_on_author_change);
    player_signal_map[11].target = author_observer_target;
    player_signal_map[11].siginfo = &window->player_handlers.on_author_change;

    player_signal_map[12].mask = HX_CALLBACK_ON_COPYRIGHT_CHANGE;
    player_signal_map[12].name = "statistic_modified";
    player_signal_map[12].callback = G_CALLBACK(playeripc_on_copyright_change);
    player_signal_map[12].target = copyright_observer_target;
    player_signal_map[12].siginfo = &window->player_handlers.on_copyright_change;

    g_assert((sizeof(player_signal_map) / sizeof(*player_signal_map)) == 13);
    
    for(i = 0; i < sizeof(player_signal_map) / sizeof(*player_signal_map); i++)
    {
        if((player_signal_map[i].mask & window->attr.scriptcallbacks) &&
            !player_signal_map[i].siginfo->is_enabled && window->window)
        {
            /* Add callbacks */
            if(player_signal_map[i].callback)
            {
                if(!player_signal_map[i].siginfo->is_enabled)
                {
                    g_assert(player_signal_map[i].target != NULL);

                    siginfo = player_signal_map[i].siginfo;                    
                    siginfo->id =
                        g_signal_connect_swapped(G_OBJECT(player_signal_map[i].target),
                                                 player_signal_map[i].name,
                                                 G_CALLBACK(player_signal_map[i].callback),
                                                 window);

                    siginfo->target = player_signal_map[i].target;
                    player_signal_map[i].siginfo->is_enabled = TRUE;
                }
            }
            else
            {
                g_warning("Unimplemented callback requested! (mask=0x%08x)\n",
                          player_signal_map[i].mask);
            }
        }
        else if(!(player_signal_map[i].mask & window->attr.scriptcallbacks) &&
                player_signal_map[i].siginfo->is_enabled)
        {
            /* Remove callbacks */
            siginfo = player_signal_map[i].siginfo;
            g_signal_handler_disconnect(G_OBJECT(siginfo->target),
                                        siginfo->id);

            siginfo->target = NULL;
            siginfo->id = 0;
            siginfo->is_enabled = FALSE;
        }        
    }            

    /* OnPosLength and OnPositionChanged are a special case, as we need
       to create a polling timer for them. */
    gboolean start_pos_length_timer = FALSE;

    if(window->attr.scriptcallbacks & HX_CALLBACK_ON_POS_LENGTH)
    {
        window->player_handlers.on_pos_length.is_enabled = TRUE;
        start_pos_length_timer = TRUE;
    }
    else
    {
        window->player_handlers.on_pos_length.is_enabled = FALSE;
    }
    
    if(window->attr.scriptcallbacks & HX_CALLBACK_ON_POSITION_CHANGE)
    {
        window->player_handlers.on_position_change.is_enabled = TRUE;
        start_pos_length_timer = TRUE;
    }
    else
    {
        window->player_handlers.on_position_change.is_enabled = FALSE;
    }

    if(start_pos_length_timer && !window->pos_len_callback_timer_is_running)
    {
        HXContentStateType state;
        
        state = hx_player_get_content_state(player);
        if(state == HX_CONTENT_STATE_PLAYING)
        {
            /* We're playing and want a timer, and one does not already
               exist. Start one. */
            gtk_timeout_add(POS_LEN_CALLBACK_INTERVAL,
                            hxembedded_window_call_pos_len_callbacks,
                            window);
            
            window->pos_len_callback_timer_is_running = TRUE;
        }
    }

    /* other player callbacks (non-signal based) */    
    player_callback_map[0].mask = HX_CALLBACK_ON_CLIP_CLOSED;
    player_callback_map[0].cbinfo = &window->player_handlers.on_clip_closed;

    player_callback_map[1].mask = HX_CALLBACK_ON_CLIP_OPENED;
    player_callback_map[1].cbinfo = &window->player_handlers.on_clip_opened;

    player_callback_map[2].mask = HX_CALLBACK_ON_POST_SEEK;
    player_callback_map[2].cbinfo = &window->player_handlers.on_post_seek;

    player_callback_map[3].mask = HX_CALLBACK_ON_PRE_SEEK;
    player_callback_map[3].cbinfo = &window->player_handlers.on_pre_seek;

    player_callback_map[4].mask = HX_CALLBACK_ON_PRESENTATION_CLOSED;
    player_callback_map[4].cbinfo = &window->player_handlers.on_presentation_closed;

    player_callback_map[5].mask = HX_CALLBACK_ON_PRESENTATION_OPENED;
    player_callback_map[5].cbinfo = &window->player_handlers.on_presentation_opened;

    g_assert((sizeof(player_callback_map) / sizeof(*player_callback_map)) == 6);
    
    for(i = 0; i < sizeof(player_callback_map) / sizeof(*player_callback_map); i++)
    {
        if(player_callback_map[i].mask & window->attr.scriptcallbacks)
        {
            /* Add callbacks */
            player_callback_map[i].cbinfo->is_enabled = TRUE;
        }
        else
        {
            player_callback_map[i].cbinfo->is_enabled = FALSE;
        }        
    }

}

void
hxembedded_window_hookup_ipc_callbacks(HXEmbeddedWindow* window)
{
    HXPlayer* player;
    
    hookup_window_ipc_callbacks(window);

    player = hxembedded_window_get_player_no_create(window);

    if(player &&
       window->title_observer  &&
       window->author_observer &&
       window->copyright_observer)
    {
        hookup_player_ipc_callbacks(window, player);
    }
}

GdkWindow*
hxembedded_window_get_browser_window(HXEmbeddedWindow* window)
{
    GdkWindow* browser_window = NULL;
    GdkWindow* win_iter;
    GdkAtom net_wm_window_type, net_wm_window_type_normal;
    gint i;
    gboolean result;
    
    net_wm_window_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE);
    net_wm_window_type_normal = gdk_atom_intern("_NET_WM_WINDOW_TYPE_NORMAL", FALSE);

    win_iter = window->window->window;

    while(win_iter && !browser_window)
    {        
        GdkAtom type;
        gint format;
        gint length;
        GdkAtom* atoms = NULL;
        GdkWindow* next = NULL;

        XID xid = GDK_WINDOW_XID(win_iter);
        
        result = gdk_property_get(win_iter,
                                  net_wm_window_type,
                                  0,
                                  0,
                                  G_MAXLONG - 3,
                                  FALSE,
                                  &type,
                                  &format,
                                  &length,
                                  (guchar**)&atoms);
        if(result)
        {
            for(i = 0; i < length; i++)
            {
                if(atoms[i] == net_wm_window_type_normal)
                {
                    browser_window = win_iter;
                }
            }
        }

        g_free(atoms);
        
        if(!browser_window)
        {

            // This is not returning the parent correctly, even after calling
            // gdk_window_foreign_new . Use the X api instead.

            // parent = gdk_window_get_parent(win_iter);

            Window root, parent;
            Window *children = NULL;
            guint nchildren;

            result = XQueryTree (GDK_DISPLAY(), xid, &root, &parent, &children, &nchildren);
            if(result)
            {
                if(children)
                {
                    XFree (children);
                }

                next = gdk_window_foreign_new(parent);
            }

            g_object_unref(win_iter);
        }

        win_iter = next;
    }

    return browser_window;
}

void
hubd_show_unsupported_browser_warning_dialog_closed(HXUnsupportedBrowserDialog* info)
{
    gboolean show_warning_in_future;
    GIOStatus status;
        
    g_return_if_fail(info != NULL);

    show_warning_in_future = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info->show_warning_in_future_checkbox));

    if(!show_warning_in_future)
    {
        // HACK! The embedded player currently can't write preferences.
        // Work around this fact by hacking in a preference into the
        // client's preference area manually.
        
        GIOChannel* chan;
        const gchar* home;
        gchar* filename;
        GError* error = NULL;
        gchar* hxplayerrc_text = NULL;
        const gchar* helix_section = "[helix]\n";
        gchar* pos;
        gsize bytes_written;
        gsize total_length;
        gsize part_1_length;
        gsize part_2_length;
    
        home = g_get_home_dir();
        g_return_if_fail(home != NULL);
    
        filename = g_strdup_printf("%s/%s", home, HXPLAYER_RC);

        chan = g_io_channel_new_file(filename, "r", &error);
        if(error)
        {
            if(error->code == G_FILE_ERROR_NOENT)
            {
                /* No hxplayerrc -- create a basic one. */
                hxplayerrc_text = g_strdup(helix_section);
            }

            g_error_free(error);
            error = NULL;
        }

        if(!error)
        {
            /* Read in the contents of the hxplayerrc */
            status = g_io_channel_read_to_end(chan,
                                              &hxplayerrc_text,
                                              &total_length,
                                              &error);
        }

        if(!error)
        {
            g_io_channel_shutdown(chan, TRUE, &error);

            pos = strstr(hxplayerrc_text, helix_section);
            pos += strlen(helix_section);
            
            part_1_length = pos - hxplayerrc_text;
            part_2_length = total_length - part_1_length;

            if(pos)
            {
                chan = g_io_channel_new_file(filename, "w", &error);
                if(!error)
                {
                    bytes_written = 0;
                    status = g_io_channel_write_chars(chan,
                                                      hxplayerrc_text,
                                                      part_1_length,
                                                      &bytes_written,
                                                      &error);
                    if(!error)
                    {
                        g_assert(bytes_written == part_1_length);
                    }
                }

            }

            if(!error)
            {
                const gchar suppress_pref[] = "SuppressUnsupportedBrowserWarning=1\n";
                gsize len = sizeof(suppress_pref) - 1;
                
                bytes_written = 0;
                status = g_io_channel_write_chars(chan,
                                                  suppress_pref,
                                                  len,
                                                  &bytes_written,
                                                  &error);
                if(!error)
                {
                    g_assert(bytes_written == len);
                }
                
            }

            if(!error)
            {
                bytes_written = 0;
                status = g_io_channel_write_chars(chan,
                                                  pos,
                                                  part_2_length,
                                                  &bytes_written,
                                                  &error);
                if(!error)
                {
                    g_assert(bytes_written == part_2_length);
                }
            }

            if(!error)
            {
                g_io_channel_shutdown(chan, TRUE, &error);
            }

            if(error)
            {
                g_warning(error->message);                
                g_error_free(error);
                error = NULL;
            }
        }
        
        g_free(filename);                    
    }
    
    g_free(info);
}

void
hxembedded_window_show_unsupported_browser_dialog(HXEmbeddedWindow* window)
{
    GtkWidget* dialog;
    GdkWindow* browser_window;
    gchar* dialog_title;
    gchar* filename;
    GladeXML* xml;
    HXUnsupportedBrowserDialog* info;
    GtkWidget* show_warning_in_future_checkbox;

    filename = hxcommon_locate_file("unsupportedbrowser.glade");
    g_return_if_fail(filename != NULL);
    
    xml = glade_xml_new (filename, NULL, NULL);
    g_free(filename);
    g_return_if_fail(xml != NULL);

    dialog = glade_xml_get_widget (xml, "hxplayer_unsupported_browser_warning_dialog");
    show_warning_in_future_checkbox = glade_xml_get_widget(xml, "hubd_show_warning_in_the_future");
                                                           
    g_assert(dialog && show_warning_in_future_checkbox);

    info = g_new0(HXUnsupportedBrowserDialog, 1);
    info->show_warning_in_future_checkbox = show_warning_in_future_checkbox;
    info->xml = xml;
    info->window = window;
    
    dialog_title = g_strdup_printf(_("%s Embedded Player Warning"), APP_NAME_LONG);
    gtk_window_set_title(GTK_WINDOW(dialog), dialog_title);
    g_free(dialog_title);
    
    browser_window = hxembedded_window_get_browser_window(window);
                
    if(browser_window)
    {
        g_signal_connect (G_OBJECT (dialog), "realize",
                          G_CALLBACK (hxcommon_embedded_transient_parent_realized),
                          browser_window);
    }
    
    g_signal_connect_swapped(G_OBJECT(dialog), "response",
                             G_CALLBACK(gtk_widget_destroy), dialog);
    
    g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
                             G_CALLBACK(hubd_show_unsupported_browser_warning_dialog_closed), info);
    
        
    gtk_widget_show(GTK_WIDGET(dialog));
}

gboolean
hew_ff(gpointer data)
{
    guint length;
    guint adjustment;
    guint position;

    HXEmbeddedWindow* window = (HXEmbeddedWindow*)data;
    HXPlayer* player = hxembedded_window_get_player(window);

    length = hx_player_get_length(player);
    position = hx_player_get_position(player);
    adjustment = length / 100;

    position += adjustment;
    if(position > length)
    {
        position = length;
    }

    hx_player_set_position(player, position);

    return TRUE; // don't remove
}

void
hxembedded_window_ff_start(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player(window);

    hx_player_start_seeking(player);
    window->ff_button->timer_id =
        gtk_timeout_add(FF_RW_UPDATE_PERIOD,
                        hew_ff,
                        window);     
}

void
hxembedded_window_ff_stop(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player(window);

    gtk_timeout_remove(window->ff_button->timer_id);
    window->ff_button->timer_id = 0;

    hx_player_stop_seeking(player);    
}

gboolean
hew_ff_start(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 1 && event->type == GDK_BUTTON_PRESS)
    {
        hxembedded_window_ff_start(window);
    }
    return FALSE; // propagate
}

gboolean
hew_ff_stop(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 1 && event->type == GDK_BUTTON_RELEASE)
    {
        hxembedded_window_ff_stop(window);
    }
    return FALSE; // propagate
}

gboolean
hew_rewind(gpointer data)
{
    guint length;
    guint adjustment;
    guint position;

    HXEmbeddedWindow* window = (HXEmbeddedWindow*)data;
    HXPlayer* player = hxembedded_window_get_player(window);

    length = hx_player_get_length(player);
    position = hx_player_get_position(player);
    adjustment = length / 100;

    if(position < adjustment)
    {
        position = 0;
    }
    else
    {
        position -= adjustment;
    }
 
    hx_player_set_position(player, position);

    return TRUE; // don't remove
}

void
hxembedded_window_rewind_start(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player(window);

    hx_player_start_seeking(player);
    window->rewind_button->timer_id =
        gtk_timeout_add(FF_RW_UPDATE_PERIOD,
                        hew_rewind,
                        window);     
}

void
hxembedded_window_rewind_stop(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player(window);

    gtk_timeout_remove(window->rewind_button->timer_id);
    window->rewind_button->timer_id = 0;

    hx_player_stop_seeking(player);    
}

gboolean
hew_rewind_start(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 1 && event->type == GDK_BUTTON_PRESS)
    {
        hxembedded_window_rewind_start(window);
    }
    return FALSE; // propagate
}

gboolean
hew_rewind_stop(HXEmbeddedWindow* window, GdkEventButton* event)
{
    if(event->button == 1 && event->type == GDK_BUTTON_RELEASE)
    {
        hxembedded_window_rewind_stop(window);
    }
    return FALSE; // propagate
}

void
hew_mute(GtkWidget* widget)
{
    HXEmbeddedWindow* window = hxembedded_window_get_window(widget);

    gboolean new_mute = TRUE;

    new_mute = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(window->mute_ctrl->button));

    hxembedded_window_mute(window, new_mute);
}

void
hxembedded_window_mute(HXEmbeddedWindow* window, gboolean new_mute)
{
    HXPlayer* player;
    gboolean mute;

    player = hxembedded_window_get_player(window);
    mute = hx_player_is_muted(HX_PLAYER(player));

    if(mute != new_mute)
    {
        /* Toggle mute */
        hx_player_set_mute(HX_PLAYER(player), new_mute);
    }    
}

void
hew_volume_changed(GtkWidget* widget)
{
    HXEmbeddedWindow* window = hxembedded_window_get_window(widget);

    hxembedded_window_volume_changed(window);
}

void
hxembedded_window_volume_changed(HXEmbeddedWindow* window)
{
    guint new_vol, vol;
    HXPlayer* player;

    g_return_if_fail(window->volume_slider != NULL); 
    g_return_if_fail(window->volume_slider->scale != NULL);

    player = hxembedded_window_get_player(window);

    new_vol = (guint)gtk_range_get_value(GTK_RANGE(window->volume_slider->scale));
    
    vol = hx_player_get_volume(player);
    if(vol != new_vol)
    {
        hx_player_set_volume(HX_PLAYER(player), new_vol);
    }
}

void
hxembedded_window_home(HXEmbeddedWindow*)
{
    /* Display the helix player webpage */    
    hxcommon_url_show("https://player.helixcommunity.org");
}

/* Common code for creating an embedded button with pixmap */
static GtkWidget*
create_controls_button(HXEmbeddedWindow* window, const gchar* pixmap_filename, GtkWidget** image_ptr, GtkWidget** alignment_ptr)
{
    GtkWidget* button;
    gchar* filename;

    *alignment_ptr = gtk_alignment_new(0.5, 0.5, 0, 0);    
    
    button = gtk_button_new();

    gtk_container_add(GTK_CONTAINER(*alignment_ptr), button);

    if(pixmap_filename)
    {
        filename = hxcommon_locate_file(pixmap_filename);        

        if(filename)
        {
            GtkWidget* image = NULL;
            
            image = gtk_image_new_from_file(filename);
            if(image)
            {
                gtk_container_add(GTK_CONTAINER(button), image);
                gtk_widget_set_size_request(image, 16, 16);

                if(image_ptr)
                {
                    *image_ptr = image;
                }
            }
            
            g_free(filename);
        }
    }
    
    return button;
}

static GtkWidget*
create_controls_ff_ctrl(HXEmbeddedWindow* window)
{
    GtkWidget* button;
    GtkWidget* alignment;
    
    button = create_controls_button(window, "fastforward.png", NULL, &alignment);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "button-press-event",
                             GTK_SIGNAL_FUNC(hew_ff_start),
                             (gpointer)window);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "button-release-event",
                             GTK_SIGNAL_FUNC(hew_ff_stop),
                             (gpointer)window);

    window->ff_button = g_new0(HXEmbeddedWindowFastForwardButton, 1);
    window->ff_button->button = button;
    
    return alignment;   
}

static GtkWidget*
create_controls_home(HXEmbeddedWindow* window)
{
    GtkWidget *button;
    GtkWidget* alignment;

    button = create_controls_button(window, "helix_ico.xpm", NULL, &alignment);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "clicked",
                             GTK_SIGNAL_FUNC(hxembedded_window_home),
                             (gpointer)window);

    return alignment;   
}

static GtkWidget*
create_controls_image_window(HXEmbeddedWindow* window)
{
    GtkWidget* hxbin;
    GValue val;
    GtkWidget* player;
    GdkColor color;
    
    hxbin = hx_bin_new(); 
    g_return_val_if_fail(hxbin != NULL, FALSE);
    hx_bin_stretch_to_fit(HX_BIN(hxbin), !window->attr.center);    
    hx_bin_maintain_aspect_ratio(HX_BIN(hxbin), window->attr.maintainaspect);

    gdk_color_parse (window->attr.backgroundcolor, &color);
    gdk_colormap_alloc_color (gtk_widget_get_colormap (hxbin),
                              &color, TRUE, TRUE);
    hx_bin_set_bg_color (HX_BIN(hxbin), &color);

    g_signal_connect_swapped(G_OBJECT(hxbin),
                             "button_press_event",
                             G_CALLBACK(hxembedded_window_popup_menu),
                             window);    

    gtk_widget_show(hxbin);

    window->image_window = g_new0(HXEmbeddedWindowImageWindow, 1);    

    window->image_window->player = hxembedded_window_get_player(window);
    window->image_window->hxbin = HX_BIN(hxbin);
    window->image_window->context_menu = hx_context_menu_new_with_player(window->image_window->player);

    player = GTK_WIDGET(window->image_window->player);
    
    if(window->attr.nologo)
    {
        hx_player_set_logo_pixmap(HX_PLAYER(player), NULL);
    }
    else
    {
        GdkPixmap* pixmap;

        if(window->window)
        {
            /* If window->window is NULL, SetWindow hasn't been called.
               The only way this can happen is if the window has a size
               of 0, in which case we don't need a logo anyway. */
            pixmap = hxcommon_get_logo_pixmap(player, window->window->window);
            hx_player_set_logo_pixmap(HX_PLAYER(player), pixmap);
            g_object_unref(G_OBJECT(pixmap));
        }
    }

    memset(&val, 0, sizeof(val));
    g_value_init(&val, G_TYPE_BOOLEAN);
    g_value_set_boolean(&val, window->attr.loop);
    g_object_set_property(G_OBJECT(player), "loop", &val);

    memset(&val, 0, sizeof(val));
    g_value_init(&val, G_TYPE_UINT);
    g_value_set_uint(&val, window->attr.numloop);
    g_object_set_property(G_OBJECT(player), "loop_count", &val);

    memset(&val, 0, sizeof(val));
    g_value_init(&val, G_TYPE_BOOLEAN);
    g_value_set_boolean(&val, window->attr.shuffle);
    g_object_set_property(G_OBJECT(player), "shuffle", &val);    
    
    gtk_widget_show(player);
        
    /* Add the video widget to hxbin */
    gtk_container_add(GTK_CONTAINER(hxbin), player);
        
    return hxbin;
}

static GtkWidget*
create_controls_info_panel(HXEmbeddedWindow* window)
{
    GtkWidget* widget;

    widget = hxstatus_display_info_panel_new();
    window->info_panel = HX_STATUS_DISPLAY_INFO_PANEL(widget);
    
    return widget;
}

static void
mute_ctrl_destroy(HXEmbeddedWindow* window)
{
    g_object_unref(window->mute_ctrl->mute_pixbuf);
    g_object_unref(window->mute_ctrl->off_pixbuf);
    g_object_unref(window->mute_ctrl->low_pixbuf);
    g_object_unref(window->mute_ctrl->mid_pixbuf);
    g_object_unref(window->mute_ctrl->high_pixbuf);
}

static GtkWidget*
create_controls_mute_ctrl(HXEmbeddedWindow* window)
{
    GtkWidget* button;
    GtkWidget* alignment;
    GtkWidget* image;
    gchar* filename;
    GError* error = NULL;

    window->mute_ctrl = g_new0(HXEmbeddedWindowMuteCtrl, 1);

    /* Load images */
    filename = hxcommon_locate_file("volume_mute.png");
    window->mute_ctrl->mute_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_off.png");
    window->mute_ctrl->off_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_low.png");
    window->mute_ctrl->low_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_mid.png");
    window->mute_ctrl->mid_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_high.png");
    window->mute_ctrl->high_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    alignment = gtk_alignment_new(0.5, 0.5, 0, 0);
    button = gtk_toggle_button_new();
    gtk_container_add(GTK_CONTAINER(alignment), button);

    image = gtk_image_new();
    gtk_container_add(GTK_CONTAINER(button), image);
    
    window->mute_ctrl->button = button;
    window->mute_ctrl->image = image;
    
    g_signal_connect(GTK_OBJECT(button),
                     "clicked",
                     GTK_SIGNAL_FUNC(hew_mute),
                     (gpointer)window);

    g_signal_connect_swapped(G_OBJECT(button),
                             "destroy",
                             G_CALLBACK(mute_ctrl_destroy),
                             (gpointer)window);
    
    
    hxwindow_update_mute_ctrl_icons(window);
    
    return alignment;       
}

static GtkWidget*
create_controls_volume_slider(HXEmbeddedWindow* window)
{
    GtkWidget* vscale;
    
    vscale = gtk_vscale_new_with_range(0.0, 100.0, 1.0);
    gtk_scale_set_draw_value(GTK_SCALE(vscale), FALSE);
    gtk_range_set_inverted(GTK_RANGE(vscale), TRUE);
    
    g_signal_connect(G_OBJECT(vscale), "value_changed",
                     G_CALLBACK(hew_volume_changed),
                     window);

    window->volume_slider = g_new0(HXEmbeddedWindowVolumeSlider, 1);
    window->volume_slider->scale = vscale;
    
    return vscale;
}


static GtkWidget*
create_controls_mute_volume(HXEmbeddedWindow* window)
{
    GtkWidget* vbox;
    GtkWidget* mute_ctrl;
    GtkWidget* volume_slider;
    
    vbox = gtk_vbox_new(FALSE, 0);

    volume_slider = create_controls_volume_slider(window);
    mute_ctrl = create_controls_mute_ctrl(window);
        
    gtk_box_pack_start(GTK_BOX(vbox), volume_slider, TRUE, TRUE, 0);        
    gtk_box_pack_start(GTK_BOX(vbox), mute_ctrl, FALSE, FALSE, 0);

    return vbox;
}

static GtkWidget*
create_controls_volume_info_panel(HXEmbeddedWindow* window)
{
    GtkWidget* hbox;
    GtkWidget* info_panel;
    GtkWidget* mute_volume;
    
    hbox = gtk_hbox_new(FALSE, 0);

    info_panel = create_controls_info_panel(window);
    mute_volume = create_controls_mute_volume(window);
    
    gtk_box_pack_start(GTK_BOX(hbox), info_panel, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), mute_volume, FALSE, FALSE, 0);    
    
    return hbox;
}

static GtkWidget*
create_controls_pause_button(HXEmbeddedWindow* window)
{
    GtkWidget *button;
    GtkWidget* alignment;

    button = create_controls_button(window, "pause.png", NULL, &alignment);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "clicked",
                             GTK_SIGNAL_FUNC(hxembedded_window_pause),
                             (gpointer)window);

    window->pause_button = g_new0(HXEmbeddedWindowPauseButton, 1);
    window->pause_button->button = button;

    return alignment;
}


static GtkWidget*
create_controls_play_button_only(HXEmbeddedWindow* window)
{
    GtkWidget* button;
    GtkWidget* alignment;

    button = create_controls_button(window, "play.png", NULL, &alignment);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "clicked",
                             GTK_SIGNAL_FUNC(hxembedded_window_play),
                             (gpointer)window);

    window->play_button = g_new0(HXEmbeddedWindowPlayButton, 1);
    window->play_button->button = button;
    
    return alignment;
}

static GtkWidget*
create_controls_play_pause_button(HXEmbeddedWindow* window)
{
    gchar* filename;
    GError* error = NULL;
    GtkWidget* alignment;

    window->play_pause_button = g_new0(HXEmbeddedWindowPlayPauseButton, 1);
    
    window->play_pause_button->button =
        create_controls_button(window, "play.png",
                               &window->play_pause_button->image,
                               &alignment);

    filename = hxcommon_locate_file("play.png");
    window->play_pause_button->play_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    if(error)
    {
        g_warning(error->message);
        g_free(error);
        error = NULL;
    }
    
    filename = hxcommon_locate_file("pause.png");
    window->play_pause_button->pause_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    if(error)
    {
        g_warning(error->message);
        g_free(error);
        error = NULL;
    }

    g_signal_connect_swapped(GTK_OBJECT(window->play_pause_button->button),
                             "clicked",
                             GTK_SIGNAL_FUNC(hxembedded_window_play_pause),
                             (gpointer)window);

    
    return alignment;
}

static GtkWidget*
create_controls_position_field(HXEmbeddedWindow* window)
{
    GtkWidget* widget;

    widget = hxstatus_display_position_field_new();

    window->position_field = HX_STATUS_DISPLAY_POSITION_FIELD(widget);
    
    return widget;
}

static GtkWidget*
create_controls_position_slider(HXEmbeddedWindow* window)
{
    GtkWidget* widget;

    widget = hxstatus_display_position_slider_new();

    window->position_slider = HX_STATUS_DISPLAY_POSITION_SLIDER(widget);
    
    return widget;
}

static GtkWidget*
create_controls_rw_ctrl(HXEmbeddedWindow* window)
{
    GtkWidget *button;
    GtkWidget* alignment;

    button = create_controls_button(window, "rewind.png", NULL, &alignment);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "button-press-event",
                             GTK_SIGNAL_FUNC(hew_rewind_start),
                             (gpointer)window);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "button-release-event",
                             GTK_SIGNAL_FUNC(hew_rewind_stop),
                             (gpointer)window);

    window->rewind_button = g_new0(HXEmbeddedWindowRewindButton, 1);
    window->rewind_button->button = button;

    return alignment;    
}

static GtkWidget*
create_controls_status_field(HXEmbeddedWindow* window)
{
    GtkWidget* widget;

    widget = hxstatus_display_status_field_new();

    window->status_field = HX_STATUS_DISPLAY_STATUS_FIELD(widget);
    
    return widget;
}

static GtkWidget*
create_controls_congestion(HXEmbeddedWindow* window)
{
    GtkWidget* widget;

    widget = hxstatus_display_congestion_new();

    window->congestion = HX_STATUS_DISPLAY_CONGESTION(widget);
    
    return widget;    
}

static GtkWidget*
create_controls_status_bar(HXEmbeddedWindow* window)
{
    GtkWidget* hbox;
    GtkWidget* bevel_frame;
    GtkWidget* status_field;
    GtkWidget* congestion;
    GtkWidget* position_field;
    GtkWidget* status_frame;
    GtkWidget* congestion_frame;
    GtkWidget* position_frame;
        
    status_field   = create_controls_status_field(window);
    congestion     = create_controls_congestion(window);
    position_field = create_controls_position_field(window);

    status_frame = gtk_frame_new(NULL);
    congestion_frame = gtk_frame_new(NULL);
    position_frame = gtk_frame_new(NULL);

    gtk_frame_set_shadow_type(GTK_FRAME(status_frame), GTK_SHADOW_IN);
    gtk_frame_set_shadow_type(GTK_FRAME(congestion_frame), GTK_SHADOW_IN);
    gtk_frame_set_shadow_type(GTK_FRAME(position_frame), GTK_SHADOW_IN);

    gtk_container_set_border_width (GTK_CONTAINER (status_field), 3);
    gtk_container_set_border_width (GTK_CONTAINER (congestion), 3);
    gtk_container_set_border_width (GTK_CONTAINER (position_field), 3);
    
    gtk_container_add(GTK_CONTAINER(status_frame), status_field);
    gtk_container_add(GTK_CONTAINER(congestion_frame), congestion);
    gtk_container_add(GTK_CONTAINER(position_frame), position_field);

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_set_spacing(GTK_BOX(hbox), 3);
    gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
    gtk_box_pack_start(GTK_BOX(hbox), status_frame, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), congestion_frame, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), position_frame, FALSE, FALSE, 0);

    bevel_frame = gtk_frame_new(NULL);
    gtk_frame_set_shadow_type(GTK_FRAME(bevel_frame), GTK_SHADOW_OUT);
    gtk_container_add(GTK_CONTAINER(bevel_frame), hbox);
    
    return bevel_frame;
}

static GtkWidget*
create_controls_stop_button(HXEmbeddedWindow* window)
{
    GtkWidget *button;
    GtkWidget* alignment;

    button = create_controls_button(window, "stop.png", NULL, &alignment);

    g_signal_connect_swapped(GTK_OBJECT(button),
                             "clicked",
                             GTK_SIGNAL_FUNC(hxembedded_window_stop),
                             (gpointer)window);

    window->stop_button = g_new0(HXEmbeddedWindowStopButton, 1);
    window->stop_button->button = button;

    return alignment;
}

static GtkWidget*
create_controls_play_pause_buttons(HXEmbeddedWindow* window)
{
#ifdef OLD_STYLE_PLAY_PAUSE_BUTTONS
    /* Create a play and pause button in an hbox */
    GtkWidget* hbox;
    GtkWidget* play_button;
    GtkWidget* pause_button;

    play_button  = create_controls_play_button_only(window);
    pause_button  = create_controls_pause_button(window);

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), play_button, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), pause_button, FALSE, FALSE, 0);
    
    return hbox;
#else
    /* Create a single play-pause button */
    return create_controls_play_pause_button(window);
#endif
}

static GtkWidget*
create_controls_tac(HXEmbeddedWindow* window)
{
    GtkWidget* widget;
    GtkWidget* bevel_frame;
    widget = hxstatus_display_tac_ctrl_new();

    gtk_container_set_border_width (GTK_CONTAINER (widget), 3);

    window->tac_ctrl = HX_STATUS_DISPLAY_TAC_CTRL(widget);
    
    bevel_frame = gtk_frame_new(NULL);
    gtk_frame_set_shadow_type(GTK_FRAME(bevel_frame), GTK_SHADOW_OUT);
    gtk_container_add(GTK_CONTAINER(bevel_frame), widget);

    return bevel_frame;
}

static void
popup_volume_update_icons(HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player_no_create(window);
    
    gboolean mute = FALSE;
    guint vol = 0;

    if(player)
    {
        mute = hx_player_is_muted(HX_PLAYER(player));
        vol = hx_player_get_volume(HX_PLAYER(player));
    }

    if(mute)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_volume_popup->image),
                                  window->mute_volume_popup->mute_pixbuf);
    }
    else if(vol == 0)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_volume_popup->image),
                                  window->mute_volume_popup->off_pixbuf);
    }
    else if(vol < 33)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_volume_popup->image),
                                  window->mute_volume_popup->low_pixbuf);
    }
    else if(vol < 66)
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_volume_popup->image),
                                  window->mute_volume_popup->mid_pixbuf);
    }
    else
    {
        gtk_image_set_from_pixbuf(GTK_IMAGE(window->mute_volume_popup->image),
                                  window->mute_volume_popup->high_pixbuf);
    }                  
}

static void
popup_volume_destroy(HXEmbeddedWindow* window)
{
    g_object_unref(window->mute_volume_popup->mute_pixbuf);
    g_object_unref(window->mute_volume_popup->off_pixbuf);
    g_object_unref(window->mute_volume_popup->low_pixbuf);
    g_object_unref(window->mute_volume_popup->mid_pixbuf);
    g_object_unref(window->mute_volume_popup->high_pixbuf);

    gtk_widget_destroy(window->mute_volume_popup->popup_window);
    g_free(window->mute_volume_popup);
    window->mute_volume_popup = NULL;
}

static GtkWidget*
create_controls_volume_popup_button(HXEmbeddedWindow* window)
{
    GtkWidget* button;
    GtkWidget* image;
    GtkWidget* alignment;
    GtkWidget* mute_volume;
    gchar* filename;
    GError* error = NULL;
    
    window->mute_volume_popup = g_new0(HXEmbeddedWindowMuteVolumePopup, 1);
        
    /* Load the pixbufs */
    filename = hxcommon_locate_file("volume_popup_mute.png");
    window->mute_volume_popup->mute_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_popup_off.png");
    window->mute_volume_popup->off_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_popup_low.png");
    window->mute_volume_popup->low_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_popup_mid.png");
    window->mute_volume_popup->mid_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    filename = hxcommon_locate_file("volume_popup_high.png");
    window->mute_volume_popup->high_pixbuf = gdk_pixbuf_new_from_file(filename, &error);
    g_free(filename);

    alignment = gtk_alignment_new(0.5, 0.5, 0, 0);    
    button = gtk_button_new();
    gtk_container_add(GTK_CONTAINER(alignment), button);

    image = gtk_image_new();
    gtk_container_add(GTK_CONTAINER(button), image);    
    
    g_signal_connect_swapped(G_OBJECT(button),
                             "clicked",
                             G_CALLBACK(hxembedded_window_show_volume_popup),
                             (gpointer)window);

    g_signal_connect_swapped(G_OBJECT(button),
                             "destroy",
                             G_CALLBACK(popup_volume_destroy),
                             (gpointer)window);

    /* Create the popup window */
    window->mute_volume_popup->popup_window = gtk_window_new(GTK_WINDOW_POPUP);
    gtk_window_set_resizable (GTK_WINDOW (window->mute_volume_popup->popup_window), FALSE);
    g_object_set_data(G_OBJECT(window->mute_volume_popup->popup_window), "hxembeddedwindow", window);

    mute_volume = create_controls_mute_volume(window);

    gtk_widget_set_size_request(mute_volume, -1, 100);
        
    gtk_container_add(GTK_CONTAINER(window->mute_volume_popup->popup_window), mute_volume);        

    window->mute_volume_popup->button = button;
    window->mute_volume_popup->image = image;
    
    popup_volume_update_icons(window);

    return alignment;
}

static GtkWidget*
create_controls_control_panel(HXEmbeddedWindow* window)
{
    GtkWidget* hbox;
    GtkWidget* bevel_frame;
    GtkWidget* play_button;
    GtkWidget* stop_button;
    GtkWidget* rew_button;
    GtkWidget* ff_button;
    GtkWidget* position_slider;
    GtkWidget* volume_popup;
    GtkWidget* image;
    gchar* filename;
    
    play_button     = create_controls_play_pause_buttons(window);
    stop_button     = create_controls_stop_button(window);
    rew_button      = create_controls_rw_ctrl(window);
    position_slider = create_controls_position_slider(window);
    ff_button       = create_controls_ff_ctrl(window);
    volume_popup    = create_controls_volume_popup_button(window);
    
    /* Create an image */
    filename = hxcommon_locate_file("embedded_logo.png");
    if(filename)
    {
        image = gtk_image_new_from_file(filename);
        g_free(filename);
    }
    
    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_set_spacing(GTK_BOX(hbox), 6);     
    gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
    gtk_box_pack_start(GTK_BOX(hbox), play_button, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), stop_button, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), rew_button, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), position_slider, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), ff_button, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), volume_popup, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);

    bevel_frame = gtk_frame_new(NULL);
    gtk_frame_set_shadow_type(GTK_FRAME(bevel_frame), GTK_SHADOW_OUT);
    gtk_container_add(GTK_CONTAINER(bevel_frame), hbox);

    return bevel_frame;
}

static GtkWidget*
create_controls_all(HXEmbeddedWindow* window)
{
    // no status widgets yet
    GtkWidget* vbox;
    GtkWidget* control_panel;
    GtkWidget* status_bar;
    GtkWidget* tac_ctrl;
    
    vbox = gtk_vbox_new(FALSE, 0);

    control_panel = create_controls_control_panel(window);
    tac_ctrl = create_controls_tac(window);
    status_bar = create_controls_status_bar(window);
        
    gtk_box_pack_start(GTK_BOX(vbox), control_panel, TRUE, TRUE, 0);        
    gtk_box_pack_start(GTK_BOX(vbox), tac_ctrl, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(vbox), status_bar, TRUE, TRUE, 0);

    return vbox;
}

static GtkWidget*
create_controls_unknown(HXEmbeddedWindow* window)
{
    window->unknown = gtk_label_new("?");

    return window->unknown;
}


gboolean
hxembedded_window_hide_volume_popup(HXEmbeddedWindow* window)
{
    gtk_widget_hide(window->mute_volume_popup->popup_window);

    return FALSE; // propagate
}

void
hxembedded_window_show_volume_popup(HXEmbeddedWindow* window,
                                      GtkWidget* widget)
{
    gint root_x, root_y;
    gint pointer_x, pointer_y;

    g_return_if_fail(window->mute_volume_popup != NULL &&
                     window->mute_volume_popup->popup_window != NULL);

    gdk_window_get_origin(widget->window, &root_x, &root_y);        
    gdk_window_get_pointer(widget->window, &pointer_x, &pointer_y, NULL);
    
    gtk_window_move(GTK_WINDOW(window->mute_volume_popup->popup_window),
                    pointer_x + root_x,
                    pointer_y + root_y);
    
    g_signal_connect_swapped(G_OBJECT(window->mute_ctrl->button),
                             "button-release-event",
                             G_CALLBACK(hxembedded_window_hide_volume_popup),
                             window);
    
    g_signal_connect_swapped(G_OBJECT(window->volume_slider->scale),
                             "button-release-event",
                             G_CALLBACK(hxembedded_window_hide_volume_popup),
                             window);
    
    gtk_widget_show_all(window->mute_volume_popup->popup_window);
}

static void
hxembedded_window_update_volume_icons(HXEmbeddedWindow* window)
{
    if(window->mute_ctrl)
    {
        hxwindow_update_mute_ctrl_icons(window);
    }

    if(window->mute_volume_popup)
    {
        popup_volume_update_icons(window);
    }
}


static void
hxembedded_window_setup_controls(HXEmbeddedWindow* window)
{
    gchar** controls_strings;
    gchar** iter;
    GtkWidget* control;
    GtkWidget* hbox = NULL;
    GtkWidget* vbox = NULL;
    guint i;
    
    static const struct
    {
        const gchar* name;
        GtkWidget* (*create_func)(HXEmbeddedWindow*);
        gboolean pack_vertical;
        gboolean expand_and_fill;
    } controls_map[] =
    {
        { "ControlPanel",    create_controls_control_panel,      TRUE,  FALSE },
        { "FFCtrl",          create_controls_ff_ctrl,            FALSE, FALSE },
        { "HomeCtrl",        create_controls_home,               FALSE, FALSE },
        { "ImageWindow",     create_controls_image_window,       TRUE,  TRUE  },
        { "InfoPanel",       create_controls_info_panel,         TRUE,  FALSE },
        { "InfoVolumePanel", create_controls_volume_info_panel,  TRUE,  FALSE },
        { "MuteCtrl",        create_controls_mute_ctrl,          FALSE, FALSE },
        { "MuteVolume",      create_controls_mute_volume,        FALSE, FALSE },
        { "PauseButton",     create_controls_pause_button,       FALSE, FALSE },
        { "PlayButton",      create_controls_play_pause_buttons, FALSE, FALSE },
        { "PlayButtonOnly",  create_controls_play_button_only,   FALSE, FALSE },
        { "PlayOnlyButton",  create_controls_play_button_only,   FALSE, FALSE },
        { "PositionField",   create_controls_position_field,     TRUE,  FALSE },
        { "PositionSlider",  create_controls_position_slider,    TRUE,  FALSE },
        { "RWCtrl",          create_controls_rw_ctrl,            FALSE, FALSE },
        { "StatusBar",       create_controls_status_bar,         TRUE,  FALSE },
        { "StatusField",     create_controls_status_field,       TRUE,  FALSE },
        { "StatusPanel",     create_controls_status_bar,         TRUE,  FALSE },
        { "StopButton",      create_controls_stop_button,        FALSE, FALSE },
        { "TACCtrl",         create_controls_tac,                TRUE,  FALSE },
        { "VolumeSlider",    create_controls_volume_slider,      FALSE, FALSE },
        { "All",             create_controls_all,                TRUE,  TRUE  },
    };
    
    vbox = gtk_vbox_new(FALSE, 5);
    
    /* We use the string version here because the order in which the
       controls occur matter here. */
    const gchar* controls_string;
    if(window->attr.controls_string)
    {
        controls_string = window->attr.controls_string;
    }
    else
    {
        controls_string = "All";
    }
    
    controls_strings = g_strsplit(controls_string, ",", 0);
    iter = controls_strings;
    while(*iter)
    {
        control = NULL;

        for(i = 0; i < sizeof(controls_map) / sizeof(*controls_map); i++)
        {            
            if(g_ascii_strcasecmp(hxcommon_strtrim(*iter), controls_map[i].name) == 0)
            {
                control = (*controls_map[i].create_func)(window);

                if(control)
                {
                    if(controls_map[i].pack_vertical)
                    {
                        gtk_box_pack_start(GTK_BOX(vbox), control, controls_map[i].expand_and_fill, controls_map[i].expand_and_fill, 0);
                    }
                    else
                    {
                        if(!hbox)
                        {
                            hbox = gtk_hbox_new(FALSE, 5);
                        }
                        gtk_box_pack_start(GTK_BOX(hbox), control, controls_map[i].expand_and_fill, controls_map[i].expand_and_fill, 0);
                    }
                }
                break;
            }
        }

        if(!control)
        {
            /* No matching control, create an unknown control as a placeholder. */
            control = create_controls_unknown(window);

            if(hbox)
            {
                gtk_box_pack_start(GTK_BOX(hbox), control, controls_map[i].expand_and_fill, controls_map[i].expand_and_fill, 0);
            }
            else
            {    
                gtk_box_pack_start(GTK_BOX(vbox), control, controls_map[i].expand_and_fill, controls_map[i].expand_and_fill, 0);
            }
        }
        
        iter++;
    }

    if(hbox)
    {
        /* add any horizontal controls to the bottom */
        gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
    }

    
    /* Add the vbox to the alignment */
    window->widget = vbox;
    gtk_container_add(GTK_CONTAINER(window->alignment), window->widget);

    g_strfreev(controls_strings);
}

void
hxembedded_window_realized(HXEmbeddedWindow* window)
{
    if(g_hxembedded_app.show_unsupported_browser_dialog &&
       !g_hxembedded_app.unsupported_browser_dialog_shown)
    {
        hxembedded_window_show_unsupported_browser_dialog(window);
        
        g_hxembedded_app.unsupported_browser_dialog_shown = TRUE;
    }
}

gboolean
hxembedded_window_set_window(HXEmbeddedWindow* window,
                             XID               xid,
                             guint             socket_id)
{
    gint width, height;

    if(window->attr.width <= 0 || window->attr.height <= 0)
    {
        g_warning("Width and height for window %d are invalid", window->id);
    }
    
    /* Reparent ourselves to this XID, and build
       the embedded window from there */

    if(window->parent_xid)
    {
        if(window->parent_xid != xid)
        {
            g_warning("Parent XID changed unexpectedly");
        }
        return TRUE;
    }
    window->parent_xid = xid;

    if(window->parent_socket_id)
    {
        if(window->parent_socket_id != socket_id)
        {
            g_warning("Parent socket changed unexpectedly");
            gtk_widget_destroy(window->window);
        }
        else
        {
            return TRUE;
        }
    }
    window->parent_socket_id = socket_id;    
    
    if(xid)
    {        
        window->window = gtk_plug_new(0);
    }
    else
    {
        window->window = gtk_plug_new(socket_id);        
    }

    g_signal_connect_swapped (G_OBJECT(window->window), "realize",
                              G_CALLBACK(hxembedded_window_realized),
                              window);
    
    gtk_widget_show(window->window);

    if(xid)
    {        
        GdkWindow* parent;
        parent = gdk_window_foreign_new(xid);
        gdk_drawable_get_size(GDK_DRAWABLE(parent),  
                              &width, &height);  
    }
    else
    {
        height = window->window->allocation.height;
        width = window->window->allocation.width;
    }
    
    g_object_set_data(G_OBJECT(window->window), "hxembeddedwindow", window);

    window->alignment = gtk_alignment_new(0.5, 0.5, 1, 1);
    gtk_container_add(GTK_CONTAINER(window->window), window->alignment);

    gtk_widget_show(window->alignment);
    gtk_widget_set_size_request(window->alignment, width, height);

    /* Sanitize the control flags */
    if(window->attr.controls & HX_CONTROLS_UNKNOWN)
    {
        if(window->attr.controls_string == NULL)
        {
            /* Check to see if the name is set to a control */
            switch(window->attr.name_flags)
            {
                case HX_NAME_PLAY_CONTROL:
                    window->attr.controls = HX_CONTROLS_PLAY_BUTTON_ONLY;
                    window->attr.controls_string = g_strdup("PlayButtonOnly");
                    break;
                
                case HX_NAME_PAUSE_CONTROL:
                    window->attr.controls = HX_CONTROLS_PAUSE_BUTTON;
                    window->attr.controls_string = g_strdup("PauseButton");
                    break;
                
                case HX_NAME_STOP_CONTROL:
                    window->attr.controls = HX_CONTROLS_STOP_BUTTON;
                    window->attr.controls_string = g_strdup("StopButton");
                    break;

                default:
                    window->attr.controls = HX_CONTROLS_UNKNOWN;
                    window->attr.controls_string = g_strdup("Unknown");
                    break;
            }
        }

        if(window->attr.controls & HX_CONTROLS_UNKNOWN)
        {
            g_warning("Unknown control");            
        }
    }
    
    hxembedded_window_setup_controls(window);
    
    /* Set the window size and reparent */
    gtk_window_resize(GTK_WINDOW(window->window), width, height);
    gtk_widget_set_size_request(window->window, width, height);

    if(xid)
    {
        GdkDrawable* win = GDK_DRAWABLE(window->window->window);
        XReparentWindow(GDK_DRAWABLE_XDISPLAY(win),
                        GDK_DRAWABLE_XID(win),
                        xid, 0, 0);

        XMapWindow(GDK_DRAWABLE_XDISPLAY(win),
                   GDK_DRAWABLE_XID(win));
    }

    gtk_widget_show_all(window->window);

    window->is_window_set = TRUE;
    
    g_assert(window->console != NULL);
    hxembedded_console_build_players_from_windows(window->console);

    return TRUE;
}

gboolean
hxembedded_window_unset_window (HXEmbeddedWindow* window)
{
    HXPlayer* player = hxembedded_window_get_player_no_create(window);

    if(player)
    {
        hx_player_stop(player);
    }

    if(window->console)
    {
        hxembedded_window_unset_console(window);
    }

    if(window->window)
    {
        gtk_widget_destroy(window->window);
        window->window = NULL;
    }

    return TRUE;
}


guint
hxembedded_window_get_id(HXEmbeddedWindow* window)
{
    return window->id;
}

G_CONST_RETURN gchar*
hxembedded_window_get_name(HXEmbeddedWindow* window)
{
    return window->attr.name;
}

HXEmbeddedWindow*
hxembedded_window_get_from_id(guint window_id)
{
    GList* consoles_iter;
    GList* windows_iter;
    HXEmbeddedConsole* console;
    HXEmbeddedWindow* window;

    consoles_iter = g_hxembedded_app.consoles_list;

    while(consoles_iter)
    {
        console = (HXEmbeddedConsole*) consoles_iter->data;	    

        windows_iter = console->windows_list;
        while(windows_iter)
        {
            window = (HXEmbeddedWindow*)windows_iter->data;
            
            if(window->id == window_id)
            {
                return window;
            }
            windows_iter = g_list_next(windows_iter);
        }        
        consoles_iter = g_list_next(consoles_iter);
    }

    g_warning("Bad window id \"%d\" requested", window_id);
    return NULL;
}

void
hxembedded_window_set_src_url(HXEmbeddedWindow* window,
                              const gchar* url)
{
    HXContentStateType state;
    
    g_return_if_fail(window->src_url == NULL);

    window->src_url = g_strdup(url);

    if(window->attr.autostart)
    {
        hxembedded_window_play(window);
    }

    state = hxembedded_window_get_content_state(window);
    if(state != HX_CONTENT_STATE_PLAYING && state != HX_CONTENT_STATE_PAUSED)
    {
        /* Set button state to stopped */
        hepw_stop(window);
    }
}


void
hxembedded_window_new_stream(HXEmbeddedWindow* window,
                             guint stream_id,
                             const gchar* url,
                             const gchar* mime_type,
                             guint stream_length)
{
    HXDataStreamInfo* stream_info;
    HXPlayer* player;
    HXDataStream* stream = NULL;
    GList* windows_list;
    GList* windows_list_iter;

    if(!window->window)
    {
        /* This rebuild deals with the case where the window hasn't had
           SetWindow called on it (the console is normally built in the
           SetWindow handler). This can happen if we don't have an
           ImageWindow, or if a dimension of the ImageWindow is 0. */
        hxembedded_console_build_players_from_windows(window->console);
    }
    
    player = hxembedded_window_get_player(window);
    g_return_if_fail(player != NULL);

    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;

    if(hx_player_get_url(player) != NULL)
    {
        while(windows_list_iter)
        {
            HXEmbeddedWindow* cur_win = (HXEmbeddedWindow*)windows_list_iter->data;
            if(cur_win->player_handlers.on_clip_closed.is_enabled)
            {
                playeripc_on_clip_closed(cur_win);
            }

            if(cur_win->player_handlers.on_presentation_closed.is_enabled)
            {
                playeripc_on_presentation_closed(cur_win);
            }

            windows_list_iter = g_list_next(windows_list_iter);
        }
    }

    /* We blacklist some mime types here to cover common webserver
       misconfigurations. */
    if(strcmp(mime_type, "text/plain") == 0 ||
       strcmp(mime_type, "text/html")  == 0 ||
       strcmp(mime_type, "application/octet-stream") == 0)
    {
        /* Extension will be used to determine content type. */
        mime_type = NULL;
    }       
    
    stream = hx_player_open_data_stream(player,
                                        url,
                                        mime_type,
                                        stream_length,
                                        TRUE);
    hx_player_play(player);

    stream_info = g_new0(HXDataStreamInfo, 1);
    stream_info->stream_id = stream_id;
    stream_info->stream = stream;
    if(mime_type)
    {        
        stream_info->mime_type = g_strdup(mime_type);
    }

    if(url)
    {        
        stream_info->url = g_strdup(url);
    }

    window->streams_list = g_list_append(window->streams_list, stream_info);
    
    windows_list_iter = windows_list;
    while(windows_list_iter)
    {    
        HXEmbeddedWindow* cur_win = (HXEmbeddedWindow*)windows_list_iter->data;
        if(cur_win->player_handlers.on_clip_opened.is_enabled)
        {
            /* We make short_name and url the same, as this seems similar
               to what realplayer for windows does (though it acutually
               only seems to use a fully qualified url for short_name */
            const char* short_name = url;
            playeripc_on_clip_opened(cur_win, short_name, url);
        }
        if(cur_win->player_handlers.on_presentation_opened.is_enabled)
        {
            playeripc_on_presentation_opened(cur_win);
        }

        windows_list_iter = g_list_next(windows_list_iter);
    }

    g_list_free(windows_list);
}

void
hxembedded_window_stream_data(HXEmbeddedWindow* window,
                              guint stream_id,
                              const gpointer data,
                              guint len)
{
    GList* iter;
    HXPlayer* player;
    iter = window->streams_list;
    HXDataStream* stream;
    while(iter)
    {
        HXDataStreamInfo* stream_info = (HXDataStreamInfo*)iter->data;
        if(stream_info->stream_id == stream_id)
        {
            stream = stream_info->stream;
            break;
        }
        iter = g_list_next(iter);
    }

    g_return_if_fail(iter != NULL);

    player = hxembedded_window_get_player(window);
    g_return_if_fail(player != NULL);

    hx_player_write_data_stream(player, stream, data, len);
}

void
hxembedded_window_stream_done(HXEmbeddedWindow* window,
                              guint stream_id)
{
    GList* iter;
    HXDataStreamInfo* stream_info;
    HXPlayer* player;
    iter = window->streams_list;
    HXDataStream* stream;
    while(iter)
    {
        stream_info = (HXDataStreamInfo*)iter->data;
        if(stream_info->stream_id == stream_id)
        {
            stream = stream_info->stream;
            break;
        }
        iter = g_list_next(iter);
    }

    g_return_if_fail(iter != NULL);

    player = hxembedded_window_get_player(window);
    g_return_if_fail(player != NULL);

    hx_player_close_data_stream(player, stream);

    window->streams_list = g_list_delete_link(window->streams_list, iter);

    if(stream_info->url)
    {
        g_free(stream_info->url);
    }

    if(stream_info->mime_type)
    {
        g_free(stream_info->mime_type);
    }

    g_free(stream_info);
}

void
hxembedded_window_set_browser_info(HXEmbeddedWindow* window,
                                   const gchar*      user_agent,
                                   gboolean          /* has_callbacks */,
                                   gboolean          /* has_xembed */)
{
    guint i;

    /* The following browsers need to implement the following spec:
       http://www.mozilla.org/projects/plugins/npruntime.html */
    
    static const gchar* unscriptable_browsers[] =
    {
        "Konqueror", /* eg: Mozilla/5.0 (compatible; Konqueror/2.1.1; X11) */
        "Opera"      /* eg: Mozilla/5.0 (UNIX; U) Opera 6.12 [en] */
    };
    
    for(i = 0; i < sizeof(unscriptable_browsers) / sizeof(*unscriptable_browsers); i++)
    {
        if(strstr(user_agent, unscriptable_browsers[i]))
        {
            // See if we've already displayed this warning in the past

            HXValue* value = NULL;
            HXEntry* entry = NULL;
            gboolean suppress_unsupported_browser_warning = FALSE;

            entry = hx_prefs_get_entry("SuppressUnsupportedBrowserWarning");
            if(entry)
            {
                value = hx_entry_get_value(entry);
                if(value)
                {
                    suppress_unsupported_browser_warning = atoi(hx_value_get_string(value));
                }
                hx_entry_free(entry);
            }

            if(!suppress_unsupported_browser_warning)
            {                
                // At this point, we don't have the GtkPlug stuff up and running,
                // so set a flag that will be checked later on, when we are in a
                // better position to show a dialog.
            
                g_hxembedded_app.show_unsupported_browser_dialog = TRUE;
            }
            break;
        }
    }
}


/* HXPlayer widget callbacks
 * =========================
 * designed to be hooked up to a HXPlayer
 */

void
hxembedded_window_play_pause(HXEmbeddedWindow* window)
{
    HXContentStateType state;
    state = hxembedded_window_get_content_state(window);

    if(state == HX_CONTENT_STATE_PLAYING)
    {
        hxembedded_window_pause(window);
    }
    else
    {
        hxembedded_window_play(window);
    }
}

void
hew_play(GtkWidget* widget)
{
    HXEmbeddedWindow* window = hxembedded_window_get_window(widget);

    hxembedded_window_play(window);
}

void
hxembedded_window_play(HXEmbeddedWindow* window)
{
    gchar* src_url = NULL;
    HXContentStateType state;
    gboolean browser_can_handle_url = TRUE;

    if(window->src_url)
    {
        src_url = window->src_url;
    }
    else if(window->attr.src)
    {
        /* This will happen if the page is using <embed src="rtsp://...
           mozilla can't stream this, so it passes through the src as is,
           and doesn't take the steps tha result in
           hxembedded_window_new_stream getting called. */
        src_url = g_strdup(window->attr.src);
    }
    else
    {
        /* Search the console for a source */
        HXEmbeddedConsole* consoles[2] = { NULL, NULL };
        GList* windows_list_iter;
        HXEmbeddedWindow* console_win;
        guint i;

        consoles[0] = window->console;
        
        if(window->console &&
           window->console == g_hxembedded_app.master_console)
        {
            consoles[1] = g_hxembedded_app.active_console;
        }
        
        for(i = 0; i < sizeof(consoles) / sizeof(*consoles); i++)
        {
            if(src_url)
            {
                break;
            }
            
            if(consoles[i])
            {
                windows_list_iter = consoles[i]->windows_list;
                while(windows_list_iter)
                {
                    console_win = (HXEmbeddedWindow*)windows_list_iter->data;
                    
                    if(console_win->src_url)
                    {
                        src_url = console_win->src_url;
                        break;
                    }
                
                    windows_list_iter = g_list_next(windows_list_iter);
                }
            }
        }
    }
    
    /* The UI should prevent us from getting here without a source */
    g_return_if_fail(src_url != NULL);

    g_hxembedded_app.active_console = window->console;


    if(strncmp(src_url, "rtsp://", sizeof("rtsp://") - 1) == 0)
    {
        browser_can_handle_url = FALSE;
    }
    
    state = hxembedded_window_get_content_state(window);

    switch(state)
    {
    case HX_CONTENT_STATE_PAUSED:
        /* Unpause */
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        g_return_if_fail(player != NULL);
        
        hx_player_play(player);
        break;
        
    case HX_CONTENT_STATE_PLAYING:
    case HX_CONTENT_STATE_CONTACTING:
    case HX_CONTENT_STATE_BUFFERING:
    default:
        break;
                
    case HX_CONTENT_STATE_NOT_LOADED:
    case HX_CONTENT_STATE_STOPPED:
        if(g_hxembedded_app.have_callbacks && browser_can_handle_url)
        {
            /* Call back into the browser to request a url, as we can then
               use the browser's proxy and cookie settings */
            if(!window->streams_list)
            {
                /* This will cause mozilla to call back via
                   hxembedded_window_new_stream, which is where
                   we actually start playback. */
                playeripc_get_url(window, src_url, NULL);
            }
        }
        else
        {
            /* No callbacks or not browser streamable, stream it ourselves. */
            HXPlayer* player;
            player = hxembedded_window_get_player(window);
            g_return_if_fail(player != NULL);
    
            hx_player_open_url(player, src_url);
            hx_player_play(player);
        }
        break;
    }
}

void hew_pause(GtkWidget* widget)
{
    HXEmbeddedWindow* window = hxembedded_window_get_window(widget);
    hxembedded_window_pause(window);
}

void
hxembedded_window_pause(HXEmbeddedWindow* window)
{
    HXPlayer* player;

    player = hxembedded_window_get_player(window);

    hx_player_pause(HX_PLAYER(player));
}

void
hew_stop(GtkWidget* widget)
{
    HXEmbeddedWindow* window = hxembedded_window_get_window(widget);
    hxembedded_window_stop(window);
}

void
hxembedded_window_stop(HXEmbeddedWindow* window)
{
    HXPlayer* player;
    
    player = hxembedded_window_get_player(window);

    hx_player_stop(HX_PLAYER(player));
}

void
hxembedded_window_previous(HXEmbeddedWindow* window)
{
    HXPlayer* player;
    guint group;
    
    player = hxembedded_window_get_player(window);

    group = hx_player_get_current_group(HX_PLAYER(player));
    
    if(group > 0)
    {
        group--;
        hx_player_set_current_group(HX_PLAYER(player),
                                       group);
    }
}

void
hxembedded_window_next(HXEmbeddedWindow* window)
{
    HXPlayer* player;
    guint group, max_group;
    
    player = hxembedded_window_get_player(window);

    max_group = hx_player_get_group_count(HX_PLAYER(player));
    group = hx_player_get_current_group(HX_PLAYER(player));
    group++;

    if(group < max_group)
    {
        hx_player_set_current_group(HX_PLAYER(player),
                                       group);
    }
}

void
hxembedded_window_start_seeking(HXEmbeddedWindow* window)
{
    if(window->enable_seek)
    {
        HXPlayer* player = hxembedded_window_get_player(window);

        hx_player_start_seeking(HX_PLAYER(player));
    }
}

void
hxembedded_window_stop_seeking(HXEmbeddedWindow* window, guint pos)
{
    if(window->enable_seek)
    {        
        GList* windows_list;
        GList* windows_list_iter;
        HXEmbeddedWindow* cur_win;
        HXPlayer* player = hxembedded_window_get_player(window);
        
        gint old_pos = hx_player_get_position(HX_PLAYER(player));

        windows_list = hxembedded_console_get_windows_list(window->console);
        windows_list_iter = windows_list;
        while(windows_list_iter)
        {
            cur_win = (HXEmbeddedWindow*)windows_list_iter->data;
            if(window->player_handlers.on_pre_seek.is_enabled)
            {
                playeripc_on_pre_seek(window, old_pos, pos);
            }
            windows_list_iter = g_list_next(windows_list_iter);
        }
            
        hx_player_set_position(player, pos);

        hx_player_stop_seeking(HX_PLAYER(player));

        windows_list_iter = windows_list;
        while(windows_list_iter)
        {
            cur_win = (HXEmbeddedWindow*)windows_list_iter->data;
            if(window->player_handlers.on_post_seek.is_enabled)
            {
                playeripc_on_post_seek(window, old_pos, pos);
            }
            windows_list_iter = g_list_next(windows_list_iter);
        }

        g_list_free(windows_list);
    }
}

gboolean
hxembedded_window_set_string_property(HXEmbeddedWindow* window, const gchar* property, const gchar* val)
{
    gboolean ret = FALSE;

    if(strcmp(property, EMBD_PROP_SOURCE) == 0)
    {
        /* Setting the source affects all players in a console */
        HXEmbeddedConsole *console = window->console;
        HXEmbeddedWindow* win;
        GList* iter = console->windows_list;
        while(iter)
        {
            win = (HXEmbeddedWindow*)iter->data;
            g_free(win->src_url);
            win->src_url = g_strdup(val);
            iter = g_list_next(iter);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CONSOLE) == 0)
    {
        hxembedded_window_set_console(window, val);
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CONTROLS) == 0)
    {
        /* destroy the control already in the gtkplug */
        if(window->widget)
        {
            gtk_container_remove(GTK_CONTAINER(window->alignment), window->widget);
            window->widget = NULL;
        }

        /* Set up the controls again */
        if(window->attr.controls_string)
        {
            g_free(window->attr.controls_string);
        }
        window->attr.controls_string = g_strdup(val);
        window->attr.controls = playeripc_control_flags_get_from_string(val);
        
        hxembedded_window_setup_controls(window);
        gtk_widget_show_all(window->widget);
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_BGCOLOR) == 0)
    {
        if(window->image_window)
        {
            GdkColor color;
            GtkWidget* widget = GTK_WIDGET(window->image_window->hxbin);
            
            if(window->attr.backgroundcolor)
            {
                g_free(window->attr.backgroundcolor);
            }
            window->attr.backgroundcolor = g_strdup(val);
        
            gdk_color_parse (val, &color);
            gdk_colormap_alloc_color (gtk_widget_get_colormap (widget),
                                      &color, TRUE, TRUE);

            hx_bin_set_bg_color (HX_BIN(window->image_window->hxbin),
                                 &color);
        
            ret = TRUE;
        }
    }
    else if(strcmp(property, EMBD_PROP_REGION) == 0)
    {
        // XXXRGG: Implementing region support is somewhat
        // convoluted, and only applies to SMIL
        g_warning("REGION attribute unimplemented");
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_AUTHOR) == 0)
    {
        if(window->author_override)
        {
            g_free(window->author_override);
        }
        window->author_override = g_strdup(val);

        if(HX_IS_STATUS_DISPLAY(window->widget))
        {
            GValue value;
            memset(&value, 0, sizeof(value));

            g_value_init(&value, G_TYPE_STRING);
            g_value_set_string_take_ownership(&value, g_strdup(val));
            g_object_set_property(G_OBJECT(window->widget), "author_override", &value);
        }
        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_TITLE) == 0)
    {
        if(window->title_override)
        {
            g_free(window->title_override);
        }
        window->title_override = g_strdup(val);
        
        if(HX_IS_STATUS_DISPLAY(window->widget))
        {
            GValue value;
            memset(&value, 0, sizeof(value));

            g_value_init(&value, G_TYPE_STRING);
            g_value_set_string_take_ownership(&value, g_strdup(val));
            g_object_set_property(G_OBJECT(window->widget), "title_override", &value);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_COPYRIGHT) == 0)
    {
        if(window->copyright_override)
        {
            g_free(window->copyright_override);
        }
        window->copyright_override = g_strdup(val);

        if(HX_IS_STATUS_DISPLAY(window->widget))
        {
            GValue value;
            memset(&value, 0, sizeof(value));

            g_value_init(&value, G_TYPE_STRING);
            g_value_set_string_take_ownership(&value, g_strdup(val));
            g_object_set_property(G_OBJECT(window->widget), "copyright_override", &value);
        }

        ret = TRUE;
    }
    else
    {
        g_warning("Unknown string property in ipc: %s\n", property);
    }            

    return ret;
}


gboolean
hxembedded_window_set_uint_property(HXEmbeddedWindow* window, gchar* property, guint val)
{
    gboolean ret = FALSE;

    /* The following are the boolean properties */
    if     (strcmp(property, EMBD_PROP_AUTOSTART) == 0)
    {
        window->attr.autostart = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_AUTOGOTOURL) == 0)
    {
        window->attr.autogotourl = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_PREFETCH) == 0)
    {
        window->attr.prefetch = (gboolean)val;
        // XXXRGG: Apply this
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_SHUFFLE) == 0)
    {
        HXPlayer* player;
        GValue value;

        window->attr.shuffle = (gboolean)val;

        memset(&val, 0, sizeof(val));
        g_value_init(&value, G_TYPE_BOOLEAN);
        g_value_set_boolean(&value, window->attr.shuffle);

        player = hxembedded_window_get_player(window);
        g_object_set_property(G_OBJECT(player), "shuffle", &value);
        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CENTER) == 0)
    {
        window->attr.center = (gboolean)val;
        if(window->image_window)
        {
            hx_bin_stretch_to_fit(HX_BIN(window->image_window->hxbin),
                                     !window->attr.center);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ASPECT) == 0)
    {
        window->attr.maintainaspect = (gboolean)val;
        if(window->image_window)
        {
            hx_bin_maintain_aspect_ratio(HX_BIN(window->image_window->hxbin),
                                            window->attr.maintainaspect);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CONSOLEEVENTS) == 0)
    {
        window->attr.consoleevents = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_LOOP) == 0)
    {
        HXPlayer* player;
        GValue value;
        
        window->attr.loop = (gboolean)val;

        memset(&val, 0, sizeof(val));
        g_value_init(&value, G_TYPE_BOOLEAN);
        g_value_set_boolean(&value, window->attr.loop);

        player = hxembedded_window_get_player(window);
        g_object_set_property(G_OBJECT(player), "loop", &value);
        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_NOLOGO) == 0)
    {
        HXPlayer* player;
        GdkPixmap* pixmap = NULL;

        player = hxembedded_window_get_player(window);
                
        window->attr.nologo = (gboolean)val;
        if(!window->attr.nologo)
        {
            if(window->window)
            {
                pixmap = hxcommon_get_logo_pixmap(GTK_WIDGET(player),
                                                  window->window->window);
            }
        }
        
        hx_player_set_logo_pixmap(player, pixmap);

        if(pixmap)
        {
            g_object_unref(G_OBJECT(pixmap));
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_IMAGESTATUS) == 0)
    {
        window->show_status_in_image_window = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CONTEXTMENU) == 0)
    {
        window->enable_context_menu = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEMSGBOX) == 0)
    {
        window->enable_message_box = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEORIGSZ) == 0)
    {
        window->enable_original_size = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEDBLSZ) == 0)
    {
        window->enable_double_size = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEFULLSCR) == 0)
    {
        window->enable_fullscreen = (gboolean)val;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DBLSZ) == 0)
    {
        g_warning("Not implemented");
        ret = FALSE;
    }
    else if(strcmp(property, EMBD_PROP_FULLSCR) == 0)
    {
        g_warning("Not implemented");
        ret = FALSE;
    }
    else if(strcmp(property, EMBD_PROP_ORIGSZ) == 0)
    {
        g_warning("Not implemented");
        ret = FALSE;
    }
    else if(strcmp(property, EMBD_PROP_CANSEEK) == 0)
    {
        GList* iter;

        iter = window->console->windows_list;
        while(iter)
        {
            window = (HXEmbeddedWindow*)iter->data;

            window->enable_seek = (gboolean)val;

            if(window->position_slider)
            {
                gtk_widget_set_sensitive(GTK_WIDGET(window->position_slider), val);
            }
            
            iter = g_list_next(iter);
        }
        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_MUTE) == 0)
    {
        hxembedded_window_mute(window, (gboolean)val);
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_KEYEVENTS) == 0)
    {
        if(val)
        {
            hxembedded_window_add_ipc_callbacks(window, HX_CALLBACK_KEY_EVENTS);
        }
        else
        {
            hxembedded_window_remove_ipc_callbacks(window, HX_CALLBACK_KEY_EVENTS);
        }

        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_MOUSEEVENTS) == 0)
    {
        if(val)
        {
            hxembedded_window_add_ipc_callbacks(window, HX_CALLBACK_MOUSE_EVENTS);
        }
        else
        {
            hxembedded_window_remove_ipc_callbacks(window, HX_CALLBACK_MOUSE_EVENTS);
        }
        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ERROREVENTS) == 0)
    {
        window->enable_error_events = val;
        if(val)
        {
            hxembedded_window_add_ipc_callbacks(window, HX_CALLBACK_ERROR_EVENTS);
        }
        else
        {
            hxembedded_window_remove_ipc_callbacks(window, HX_CALLBACK_ERROR_EVENTS);
        }
        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ABOUTDLG) == 0)
    {
        if(!window->about_dialog)
        {
            HXPlayer* player;
            player = hxembedded_window_get_player(window);

            window->about_dialog = hxplay_about_dialog_new (player, NULL, NULL);
            if(window->about_dialog)
            {
                GdkWindow* browser_window;

                browser_window = hxembedded_window_get_browser_window(window);

                if(browser_window)
                {
                    g_signal_connect (G_OBJECT (window->about_dialog), "realize",
                                      G_CALLBACK (hxcommon_embedded_transient_parent_realized),
                                      browser_window);
                }

                g_signal_connect_after (G_OBJECT(window->about_dialog),
                                        "response",
                                        G_CALLBACK(hxcommon_close_dialog),
                                        &window->about_dialog);
            }
        }

        if(window->about_dialog)
        {
            gtk_widget_show_all(GTK_WIDGET(window->about_dialog));
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_PREFSDLG) == 0)
    {
        if(!window->preferences_dialog)
        {
            HXPlayer* player;
            player = hxembedded_window_get_player(window);

            window->preferences_dialog = hxplay_preferences_dialog_new (player,
                                                                        NULL);
            if(window->preferences_dialog)
            {
                GdkWindow* browser_window;

                browser_window = hxembedded_window_get_browser_window(window);

                if(browser_window)
                {
                    g_signal_connect (G_OBJECT (window->preferences_dialog), "realize",
                                      GTK_SIGNAL_FUNC (hxcommon_embedded_transient_parent_realized),
                                      browser_window);
                }

                g_signal_connect_after (G_OBJECT(window->preferences_dialog),
                                        "response",
                                        G_CALLBACK(hxcommon_close_dialog),
                                        &window->preferences_dialog);
            }
        }

        if(window->preferences_dialog)
        {
            gtk_widget_show_all(GTK_WIDGET(window->preferences_dialog));
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_STATSDLG) == 0)
    {
        if(!window->statistics_dialog)
        {
            HXPlayer* player;
            player = hxembedded_window_get_player(window);

            window->statistics_dialog = hxplay_statistics_dialog_new (player);
            if(window->statistics_dialog)
            {
                GdkWindow* browser_window;
                    
                browser_window = hxembedded_window_get_browser_window(window);

                if(browser_window)
                {
                    g_signal_connect (G_OBJECT (window->statistics_dialog),
                                      "realize",
                                      GTK_SIGNAL_FUNC (hxcommon_embedded_transient_parent_realized),
                                      browser_window);
                }
                
                g_signal_connect_after (G_OBJECT(window->statistics_dialog),
                                        "response",
                                        G_CALLBACK(hxcommon_close_dialog),
                                        &window->statistics_dialog);
            }
        }

        if(window->statistics_dialog)
        {
            gtk_widget_show_all(GTK_WIDGET(window->statistics_dialog));
        }

        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DOPLAY) == 0)
    {
        if(val)
        {
            hxembedded_window_play(window);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DOPLAYPAUSE) == 0)
    {
        if(val)
        {
            hxembedded_window_play_pause(window);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DOPAUSE) == 0)
    {
        if(val)
        {
            hxembedded_window_pause(window);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DOSTOP) == 0)
    {
        if(val)
        {
            hxembedded_window_stop(window);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DONEXT) == 0)
    {
        if(val)
        {
            hxembedded_window_next(window);
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DOPREV) == 0)
    {
        if(val)
        {
            hxembedded_window_previous(window);
        }
        ret = TRUE;
    }

    /* The following are the integer properties */
    else if(strcmp(property, EMBD_PROP_NUMLOOPS) == 0)
    {
        HXPlayer* player;
        GValue value;
        
        window->attr.numloop = val;

        memset(&val, 0, sizeof(val));
        g_value_init(&value, G_TYPE_UINT);
        g_value_set_uint(&value, window->attr.numloop);
        
        player = hxembedded_window_get_player(window);
        g_object_set_property(G_OBJECT(player), "loop_count", &value);

        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_VOLUME) == 0)
    {
        HXPlayer* player;
        
        player = hxembedded_window_get_player(window);
        hx_player_set_volume(HX_PLAYER(player), val);
        
        ret = TRUE;
    }
    else
    {
        g_warning("Unknown integer/boolean property in ipc: %s\n", property);
    }

    return ret;
}

static void
print_property(gchar** str_ptr, size_t len, const gchar* str)
{
    /* Prints a property, allocating memory if needed */    
    if(len > 0)
    {
        strncpy(*str_ptr, str, len - 1);
        (*str_ptr)[len - 1] = '\0';
    }
    else
    {
        *str_ptr = g_strdup(str);
    }
}

gboolean
hxembedded_window_get_string_property(HXEmbeddedWindow* window, const gchar* property, gchar** val, guint len)
{
    gboolean ret = FALSE;
    HXPlayer* player;
    const gchar* prop;
    player = hxembedded_window_get_player(window);

    *val = NULL;

    if(strcmp(property, EMBD_PROP_SOURCE) == 0)
    {
        /* XXXRGG: if the src is null, should we return a src
           in this console that isn't null? */
        if(window->src_url)
        {
            print_property(val, len, window->src_url);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_CONSOLE) == 0)
    {
        if(window->attr.console)
        {
            print_property(val, len, window->attr.console);        
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_CONTROLS) == 0)
    {
        if(window->attr.controls_string)
        {
            print_property(val, len, window->attr.controls_string);        
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_BGCOLOR) == 0)
    {
        if(window->attr.backgroundcolor)
        {
            print_property(val, len, window->attr.backgroundcolor);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_REGION) == 0)
    {
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_AUTHOR) == 0)
    {
        if(window->author_override)
        {
            print_property(val, len, window->author_override);
            ret = TRUE;
        }
        else
        {
            GValue value;
            memset(&value, 0, sizeof(value));
            ret = hx_player_get_statistic(player, "author", &value);
            if(ret)
            {
                prop = g_value_get_string(&value);                
                print_property(val, len, prop);
                g_value_unset(&value);
                ret = TRUE;
            }
        }
    }
    else if(strcmp(property, EMBD_PROP_TITLE) == 0)
    {
        if(window->title_override)
        {
            print_property(val, len, window->title_override);
            ret = TRUE;
        }
        else
        {
            GValue value;
            memset(&value, 0, sizeof(value));
            ret = hx_player_get_statistic(player, "title", &value);
            if(ret)
            {
                prop = g_value_get_string(&value);                
                print_property(val, len, prop);
                g_value_unset(&value);
                ret = TRUE;
            }
        }
    }
    else if(strcmp(property, EMBD_PROP_COPYRIGHT) == 0)
    {
        if(window->copyright_override)
        {
            print_property(val, len, window->copyright_override);
            ret = TRUE;
        }
        else
        {
            GValue value;
            memset(&value, 0, sizeof(value));
            ret = hx_player_get_statistic(player, "copyright", &value);
            if(ret)
            {
                prop = g_value_get_string(&value);                
                print_property(val, len, prop);
                g_value_unset(&value);
                ret = TRUE;
            }
        }
    }
    else if(strcmp(property, EMBD_PROP_LANGSTR) == 0)
    {
        const gchar* result;
        // XXXRGG: FIXME
        result = "fixme";
        if(result)
        {
            print_property(val, len, result);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_STATUS) == 0)
    {
        if(window->status_message)
        {
            print_property(val, len, window->status_message);
            ret = TRUE;            
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_ERRORURL) == 0)
    {
        if(window->helix_more_info_url)
        {
            print_property(val, len, window->helix_more_info_url);
            ret = TRUE;            
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_ERRORUSERSTR) == 0)
    {
        if(window->helix_user_string)
        {
            print_property(val, len, window->helix_user_string);
            ret = TRUE;            
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_ERRORRMASTR) == 0)
    {
        if(window->helix_error_string)
        {
            print_property(val, len, window->helix_error_string);
            ret = TRUE;            
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_VERSION) == 0)
    {
        print_property(val, len, TARVER_STRING_VERSION);
        ret = TRUE;            
    }
    else
    {
        g_warning("Unknown string property in ipc: %s\n", property);
    }            

    return ret;
}

gboolean
hxembedded_window_get_uint_property(HXEmbeddedWindow* window, gchar* property, guint* val)
{    
    gboolean ret = FALSE;

    *val = 0;
    
    /* The following are the boolean properties */
    if(strcmp(property, EMBD_PROP_AUTOSTART) == 0)
    {
        *val = window->attr.autostart;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_AUTOGOTOURL) == 0)
    {
        *val = window->attr.autogotourl;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_PREFETCH) == 0)
    {
        *val = window->attr.prefetch;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_SHUFFLE) == 0)
    {
        *val = window->attr.shuffle;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CENTER) == 0)
    {
        *val = window->attr.center;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ASPECT) == 0)
    {
        *val = window->attr.maintainaspect;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CONSOLEEVENTS) == 0)
    {
        *val = window->attr.consoleevents;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_LOOP) == 0)
    {
        *val = window->attr.loop;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_NOLOGO) == 0)
    {
        *val = window->attr.nologo;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_IMAGESTATUS) == 0)
    {
        *val = window->show_status_in_image_window;        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CONTEXTMENU) == 0)
    {
        *val = window->enable_context_menu;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEMSGBOX) == 0)
    {
        *val = window->enable_message_box;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEORIGSZ) == 0)
    {
        *val = window->enable_original_size;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEDBLSZ) == 0)
    {
        *val = window->enable_double_size;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ENABLEFULLSCR) == 0)
    {
        *val = window->enable_fullscreen;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_DBLSZ) == 0)
    {
        *val = window->enable_double_size;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_FULLSCR) == 0)
    {
        g_warning("Not implemented");                
        ret = FALSE;
    }
    else if(strcmp(property, EMBD_PROP_ORIGSZ) == 0)
    {
        g_warning("Not implemented");
        ret = FALSE;
    }
    else if(strcmp(property, EMBD_PROP_CANSEEK) == 0)
    {
        *val = window->enable_seek;        
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_PLUS) == 0)
    {
        *val = TRUE;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_LIVE) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);

        if(player)
        {
            *val = hx_player_is_live(HX_PLAYER(player));
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_CANPLAYPAUSE) == 0)
    {
        HXContentStateType state;
        state = hxembedded_window_get_content_state(window);
        switch(state)
        {
            case HX_CONTENT_STATE_NOT_LOADED:
            case HX_CONTENT_STATE_CONTACTING:
            case HX_CONTENT_STATE_BUFFERING:
            default:
                *val = FALSE;
                break;
                
            case HX_CONTENT_STATE_STOPPED:
            case HX_CONTENT_STATE_PLAYING:
            case HX_CONTENT_STATE_PAUSED:
                *val = TRUE;
                break;
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CANPLAY) == 0)
    {
        HXContentStateType state;
        state = hxembedded_window_get_content_state(window);
        switch(state)
        {
            case HX_CONTENT_STATE_PLAYING:
            default:
                *val = FALSE;
                break;
                    
            case HX_CONTENT_STATE_NOT_LOADED:
            case HX_CONTENT_STATE_CONTACTING:
            case HX_CONTENT_STATE_BUFFERING:
            case HX_CONTENT_STATE_STOPPED:
            case HX_CONTENT_STATE_PAUSED:
                *val = TRUE;
                break;
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CANPAUSE) == 0)
    {
        HXContentStateType state;
        state = hxembedded_window_get_content_state(window);
        switch(state)
        {
            case HX_CONTENT_STATE_NOT_LOADED:
            case HX_CONTENT_STATE_CONTACTING:
            case HX_CONTENT_STATE_BUFFERING:
            case HX_CONTENT_STATE_PAUSED:
            case HX_CONTENT_STATE_STOPPED:
            default:
                *val = FALSE;
                break;
                
            case HX_CONTENT_STATE_PLAYING:
                *val = TRUE;
                break;
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_CANSTOP) == 0)
    {
        HXContentStateType state;
        state = hxembedded_window_get_content_state(window);
        switch(state)
        {
            case HX_CONTENT_STATE_NOT_LOADED:
            case HX_CONTENT_STATE_STOPPED:
            default:
                *val = FALSE;
                break;
                    
            case HX_CONTENT_STATE_CONTACTING:
            case HX_CONTENT_STATE_BUFFERING:
            case HX_CONTENT_STATE_PAUSED:
            case HX_CONTENT_STATE_PLAYING:
                *val = TRUE;
                break;
        }
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_PLAYSTATE) == 0)
    {
        HXContentStateType state;
        int javascript_api_state;
        
        state = hxembedded_window_get_content_state(window);
        if(window->is_seeking)
        {
            javascript_api_state = 5;
        }
        else
        {
            switch(state)
            {
                case HX_CONTENT_STATE_STOPPED:
                case HX_CONTENT_STATE_NOT_LOADED:
                default:
                    javascript_api_state = 0;
                    break;
                
                case HX_CONTENT_STATE_CONTACTING:
                    javascript_api_state = 1;
                    break;
                
                case HX_CONTENT_STATE_BUFFERING:
                    javascript_api_state = 2;
                    break;
                    
                case HX_CONTENT_STATE_PLAYING:
                    javascript_api_state = 3;
                    break;

                case HX_CONTENT_STATE_PAUSED:
                    javascript_api_state = 4;
                    break;
            }
        }
        
        *val = javascript_api_state;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_HASNEXTENTRY) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);

        if(player)
        {
            guint index;
            guint count;
            
            index = hx_player_get_current_group(HX_PLAYER(player));
            count = hx_player_get_group_count(HX_PLAYER(player));

            if(index < (count - 1))
            {
                *val = TRUE;
            }
            else
            {
                *val = FALSE;
            }
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_HASPREVENTRY) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);

        if(player)
        {
            guint index;
            guint count;
            
            index = hx_player_get_current_group(HX_PLAYER(player));
            count = hx_player_get_group_count(HX_PLAYER(player));

            if(index == 0)
            {
                *val = FALSE;
            }
            else
            {
                *val = TRUE;
            }
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_MUTE) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_is_muted(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_KEYEVENTS) == 0)
    {        
        *val = window->attr.scriptcallbacks & HX_CALLBACK_KEY_EVENTS;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_MOUSEEVENTS) == 0)
    {
        *val = window->attr.scriptcallbacks & HX_CALLBACK_MOUSE_EVENTS;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ERROREVENTS) == 0)
    {
        *val = window->enable_error_events;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_STEREO) == 0)
    {
        g_warning("Unimplemented");
        ret = FALSE;
    }

    /* The following are the integer properties */
    else if(strcmp(property, EMBD_PROP_NUMLOOPS) == 0)
    {
        *val = window->attr.numloop;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_VOLUME) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_volume(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }
    }
    else if(strcmp(property, EMBD_PROP_PKTSTOTAL) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "Total", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_PKTSRECV) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "Received", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_PKTSORDER) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "OutOfOrder", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_PKTSMISSING) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "Lost", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_PKTSLATE) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "Late", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_PKTSEARLY) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "Early", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_BWAVG) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "AverageBandwidth", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_BWCUR) == 0)
    {
        GValue value;
        HXPlayer* player;
        memset(&value, 0, sizeof(value));
        
        player = hxembedded_window_get_player(window);
        ret = hx_player_get_statistic(player, "CurrentBandwidth", &value);
        if(ret)
        {
            *val = g_value_get_int(&value);
            g_value_unset(&value);
        }        
    }
    else if(strcmp(property, EMBD_PROP_BWCONN) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_clip_bandwidth(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_HEIGHT) == 0)
    {
        *val = window->attr.height;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_WIDTH) == 0)
    {
        *val = window->attr.width;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_NUMENTRIES) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_group_count(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_CURENTRY) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_current_group(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }    
    else if(strcmp(property, EMBD_PROP_BUFELAPSED) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_position(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_BUFREMAINING) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            guint length;
            length = hx_player_get_length(player);
            *val = length - hx_player_get_position(player);
            
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_LANGID) == 0)
    {
        g_warning("Unimplemented");        
        ret = FALSE;
    }        
    else if(strcmp(property, EMBD_PROP_COUNTRYID) == 0)
    {
        g_warning("Unimplemented");
        ret = FALSE;
    }
    else if(strcmp(property, EMBD_PROP_NUMSOURCES) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_source_count(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_ERRORRMACODE) == 0)
    {
        *val = window->helix_error_code;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ERRORUSERCODE) == 0)
    {
        *val = window->helix_user_code;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_ERRORSEVERITY) == 0)
    {
        // Values are:
        //  0: Panic
        //  1: Severe
        //  2: Critical
        //  3: General
        //  4: Warning
        //  5: Notice
        //  6: Informational
        //  7: Debug
        //  
        // XXXRGG: hxclientkit doesn't give us the severity.
        *val = 3;
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_POSITION) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_position(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_LENGTH) == 0)
    {
        HXPlayer* player;
        player = hxembedded_window_get_player(window);
        if(player)
        {
            *val = hx_player_get_length(player);
            ret = TRUE;
        }
        else
        {
            ret = FALSE;
        }        
    }
    else if(strcmp(property, EMBD_PROP_ABOUTDLG) == 0)
    {
        *val = (window->about_dialog != NULL);
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_PREFSDLG) == 0)
    {
        *val = (window->preferences_dialog != NULL);
        ret = TRUE;
    }
    else if(strcmp(property, EMBD_PROP_STATSDLG) == 0)
    {
        *val = (window->statistics_dialog != NULL);
        ret = TRUE;
    }
    else
    {
        g_warning("Unknown integer/boolean property in ipc: %s\n", property);
    }

    return ret;
}

gboolean
hxembedded_window_get_entry_string_property(HXEmbeddedWindow* window, gchar* property, guint index, gchar** val, guint len)
{
    gboolean ret = FALSE;
    const gchar* prop;
    HXPlayer* player;    
    
    *val = NULL;

    player = hxembedded_window_get_player(window);
    
    // XXXRGG: Need to figure out how to retrieve clip info for a
    // given group.
    (void)index;
    
    if(strcmp(property, EMBD_PROP_ENTRYTITLE) == 0)
    {
        GValue value;
        memset(&value, 0, sizeof(value));
        ret = hx_player_get_statistic(player, "title", &value);
        if(ret)
        {
            prop = g_value_get_string(&value);                
            print_property(val, len, prop);
            g_value_unset(&value);
            ret = TRUE;
        }
    }
    else if(strcmp(property, EMBD_PROP_ENTRYAUTHOR) == 0)
    {
        GValue value;
        memset(&value, 0, sizeof(value));
        ret = hx_player_get_statistic(player, "author", &value);
        if(ret)
        {
            prop = g_value_get_string(&value);                
            print_property(val, len, prop);
            g_value_unset(&value);
            ret = TRUE;
        }
    }
    else if(strcmp(property, EMBD_PROP_ENTRYCOPY) == 0)
    {
        GValue value;
        memset(&value, 0, sizeof(value));
        ret = hx_player_get_statistic(player, "copyright", &value);
        if(ret)
        {
            prop = g_value_get_string(&value);                
            print_property(val, len, prop);
            g_value_unset(&value);
            ret = TRUE;
        }
    }
    else if(strcmp(property, EMBD_PROP_ENTRYABSTRACT) == 0)
    {
        GValue value;
        memset(&value, 0, sizeof(value));
        ret = hx_player_get_statistic(player, "abstract", &value);
        if(ret)
        {
            prop = g_value_get_string(&value);                
            print_property(val, len, prop);
            g_value_unset(&value);
            ret = TRUE;
        }
    }
    else if(strcmp(property, EMBD_PROP_SOURCETRANS) == 0)
    {
        GValue value;
        memset(&value, 0, sizeof(value));

        // XXXRGG: Is Source0.TransortMode right?
        ret = hx_player_get_statistic(player, "Source0.TransortMode", &value);
        if(ret)
        {
            prop = g_value_get_string(&value);                
            print_property(val, len, prop);
            g_value_unset(&value);
            ret = TRUE;
        }
    }
    else
    {
        g_warning("Unknown entry string property in ipc: %s\n", property);
    }    

    return ret;
}



void
hepw_request_upgrade(HXEmbeddedWindow* window, const gchar* url, GList* components_list, gboolean blocking, gpointer)
{
    if(!window->enable_error_events)
    {
        GdkWindow* browser_window;

        browser_window = hxembedded_window_get_browser_window(window);

        hxcommon_run_request_upgrade_dialog(NULL,
                                            browser_window,
                                            url,
                                            components_list,
                                            blocking);
    }
}

void
hepw_request_authentication(HXEmbeddedWindow* window,
                            const gchar* server_proxy,
                            const gchar* realm,
                            gboolean is_proxy_server)
{
    HXPlayer* player;
    GdkWindow* browser_window;

    player = hxembedded_window_get_player(window);

    browser_window = hxembedded_window_get_browser_window(window);
    
    hxcommon_run_authentication_dialog(NULL,
                                       browser_window,
                                       HX_PLAYER(player),
                                       server_proxy,
                                       realm,
                                       is_proxy_server);
}

void
hepw_hxerror(HXEmbeddedWindow* window,
             guint hxcode,
             guint user_code,
             const gchar* error_string,
             const gchar* user_string,
             const gchar* more_info_url)
{
    if(!window->enable_error_events)
    {
        GdkWindow* browser_window;
        
        browser_window = hxembedded_window_get_browser_window(window);
        
        hxcommon_run_error_dialog(NULL,
                                  browser_window,
                                  hxcode,
                                  user_code,
                                  error_string,
                                  user_string,
                                  more_info_url);
    }

    window->helix_error_code = hxcode;
    window->helix_user_code = user_code;
    
    if(window->helix_error_string)
    {
        g_free(window->helix_error_string);
    }
    window->helix_error_string = g_strdup(error_string);

    if(window->helix_user_string)
    {
        g_free(window->helix_user_string);
    }
    window->helix_user_string = g_strdup(user_string);

    if(window->helix_more_info_url)
    {
        g_free(window->helix_more_info_url);
    }
    window->helix_more_info_url = g_strdup(more_info_url);    
}

void
hepw_start_seeking(HXEmbeddedWindow* window)
{
    GList* windows_list;
    GList* windows_list_iter;
    
    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;
    
    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        window->is_seeking = TRUE;
        
        windows_list_iter = g_list_next(windows_list_iter);
    }                

    g_list_free(windows_list);
}

void
hepw_stop_seeking(HXEmbeddedWindow* window)
{
    GList* windows_list;
    GList* windows_list_iter;
    
    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;
    
    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        window->is_seeking = FALSE;
        
        windows_list_iter = g_list_next(windows_list_iter);
    }                

    g_list_free(windows_list);    
}


void
hepw_play_buffer_contact(HXEmbeddedWindow* window)
{
    GList* windows_list;
    GList* windows_list_iter;
    gboolean is_live = FALSE;
    HXPlayer* player;
    
    /* If we have OnPosLength or OnPositionChanged callbacks,
       start calling them. This callback will remove itself when
       it is no longer required. */
    if((window->player_handlers.on_position_change.is_enabled ||
        window->player_handlers.on_pos_length.is_enabled))
    {
        gtk_timeout_add(POS_LEN_CALLBACK_INTERVAL,
                        hxembedded_window_call_pos_len_callbacks,
                        window);
    }

    player = hxembedded_window_get_player(window);
    if(player)
    {
        is_live = hx_player_is_live(player);
    }
    
    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;
    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        if(window->play_button)
        {
            gtk_widget_set_sensitive(window->play_button->button, FALSE);
        }            

        if(window->pause_button)
        {
            gtk_widget_set_sensitive(window->pause_button->button, TRUE);
        }            

        if(window->stop_button)
        {
            gtk_widget_set_sensitive(window->stop_button->button, TRUE);
        }            

        if(window->play_pause_button)
        {
            gtk_widget_set_sensitive(window->play_pause_button->button, TRUE);
            gtk_image_set_from_pixbuf(GTK_IMAGE(window->play_pause_button->image),
                                      window->play_pause_button->pause_pixbuf);
        }        

        if(window->ff_button)
        {
            gtk_widget_set_sensitive(window->ff_button->button, !is_live);            
        }

        if(window->rewind_button)
        {
            gtk_widget_set_sensitive(window->rewind_button->button, !is_live);
        }

        windows_list_iter = g_list_next(windows_list_iter);
    }

    g_list_free(windows_list);
}

void
hepw_pause(HXEmbeddedWindow* window)
{
    GList* windows_list;
    GList* windows_list_iter;
    gboolean is_live = FALSE;
    HXPlayer* player;
    
    player = hxembedded_window_get_player(window);
    if(player)
    {
        is_live = hx_player_is_live(player);
    }

    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;
    
    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        if(window->play_button)
        {
            gtk_widget_set_sensitive(window->play_button->button, window->is_seeking);
        }            

        if(window->pause_button)
        {
            gtk_widget_set_sensitive(window->pause_button->button, !window->is_seeking);
        }            

        if(window->stop_button)
        {
            gtk_widget_set_sensitive(window->stop_button->button, TRUE);
        }            

        if(window->play_pause_button)
        {
            gtk_widget_set_sensitive(window->play_pause_button->button, TRUE);

            if(window->is_seeking)
            {
                gtk_image_set_from_pixbuf(GTK_IMAGE(window->play_pause_button->image),
                                          window->play_pause_button->pause_pixbuf);
            }
            else
            {
                gtk_image_set_from_pixbuf(GTK_IMAGE(window->play_pause_button->image),
                                          window->play_pause_button->play_pixbuf);
            }
        }                

        if(window->ff_button)
        {
            gtk_widget_set_sensitive(window->ff_button->button, !is_live);            
        }

        if(window->rewind_button)
        {
            gtk_widget_set_sensitive(window->rewind_button->button, !is_live);
        }

        windows_list_iter = g_list_next(windows_list_iter);
    }                

    g_list_free(windows_list);
}

void
hepw_stop(HXEmbeddedWindow* window)
{
    GList* windows_list;
    GList* windows_list_iter;
    gboolean is_live = FALSE;
    HXPlayer* player;

    player = hxembedded_window_get_player(window);
    if(player)
    {
        is_live = hx_player_is_live(player);
    }

    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;

    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        if(window->play_button)
        {
            gtk_widget_set_sensitive(window->play_button->button, TRUE);
        }            

        if(window->pause_button)
        {
            gtk_widget_set_sensitive(window->pause_button->button, FALSE);
        }            

        if(window->stop_button)
        {
            gtk_widget_set_sensitive(window->stop_button->button, FALSE);
        }            

        if(window->play_pause_button)
        {
            gtk_widget_set_sensitive(window->play_pause_button->button, TRUE);
            gtk_image_set_from_pixbuf(GTK_IMAGE(window->play_pause_button->image),
                                      window->play_pause_button->play_pixbuf);
        }        
        
        if(window->ff_button)
        {
            gtk_widget_set_sensitive(window->ff_button->button, FALSE);
        }

        if(window->rewind_button)
        {
            gtk_widget_set_sensitive(window->rewind_button->button, FALSE);
        }

        windows_list_iter = g_list_next(windows_list_iter);
    }

    g_list_free(windows_list);
}

void
hepw_volume_changed(HXEmbeddedWindow* window, guint vol)
{
    GList* windows_list;
    GList* windows_list_iter;

    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;

    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        if(window->volume_slider)
        {
            gtk_range_set_value(GTK_RANGE(window->volume_slider->scale),
                                (gdouble) vol);
        }
        if(window->mute_ctrl)
        {
            hxwindow_update_mute_ctrl_icons(window);
        }

        if(window->mute_volume_popup)
        {
            popup_volume_update_icons(window);
        }
    
        windows_list_iter = g_list_next(windows_list_iter);
    }

    g_list_free(windows_list);
}

void
hepw_mute_changed(HXEmbeddedWindow* window, gboolean mute)
{
    GList* windows_list;
    GList* windows_list_iter;

    windows_list = hxembedded_console_get_windows_list(window->console);
    windows_list_iter = windows_list;

    while(windows_list_iter)
    {
        window = (HXEmbeddedWindow*)windows_list_iter->data;

        if(window->mute_ctrl)
        {
            hxembedded_window_update_volume_icons (window);
        }            
            
        windows_list_iter = g_list_next(windows_list_iter);
    }

    g_list_free(windows_list);
}

void
hepw_goto_url(HXEmbeddedWindow* window, gchar* url, gchar* target)
{
    playeripc_get_url(window, url, target);
}

void
hepw_open_window(HXEmbeddedWindow* window, gchar* url, gchar* target)
{
    /* XXXRGG: Implement me */
    g_warning("openwindow from embedded player unimplemented");
}


/* Setup/Destroy functions called from hxcommon.cpp
 * ================================================
 */
gboolean
hxembedded_init(HXCommandLineOptions* cmd)
{
    gboolean result;

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

    hxcommon_load_preferences(NULL);
    
    result = playeripc_init(cmd->command_fd, cmd->callbacks_fd);
    if(!result)
    {
        g_warning("playeripc_init failed");
    }

    if(cmd->callbacks_fd >= 0)
    {
        g_hxembedded_app.have_callbacks = TRUE;
    }
    else
    {
        g_hxembedded_app.have_callbacks = FALSE;        
    }
    
    return result;
}

void
hxembedded_destroy(void)
{
    GList* consoles_iter;
    GList* windows_iter;
    GList* streams_iter;
        
    consoles_iter = g_hxembedded_app.consoles_list;
    while(consoles_iter)
    {
        HXEmbeddedConsole* console = (HXEmbeddedConsole*)consoles_iter->data;

        windows_iter = console->windows_list;
        while(windows_iter)
        {
            HXEmbeddedWindow* window = (HXEmbeddedWindow*)windows_iter->data;

            g_free(window->attr.console);
            g_free(window->attr.controls_string);
            g_free(window->attr.name);
            g_free(window->attr.src);
            g_free(window->attr.type);

            streams_iter = window->streams_list;
            while(streams_iter)
            {
                HXDataStreamInfo* stream = (HXDataStreamInfo*)streams_iter->data;
                
                g_free(stream->url);
                g_free(stream->mime_type);
                g_free(stream);
                
                streams_iter = g_list_next(streams_iter);
            }

            g_free(window->src_url);
            g_free(window->helix_error_string);
            g_free(window->helix_user_string);
            g_free(window->helix_more_info_url);

            g_free(window->author_override);
            g_free(window->title_override);
            g_free(window->copyright_override);

            g_free(window->mute_ctrl);
            g_free(window->volume_slider);
            g_free(window->play_button);
            g_free(window->pause_button);
            g_free(window->stop_button);
            g_free(window->play_pause_button);
            g_free(window->ff_button);
            g_free(window->rewind_button);
            g_free(window->status_message);

            if(window->image_window)
            {
                if(window->image_window->context_menu)
                {
                    hx_context_menu_destroy(window->image_window->context_menu);
                }

                g_free(window->image_window);
            }

            /* Free callback signal info */
            window->window = NULL;
            window->attr.scriptcallbacks = (HXCallbackFlags)0;
            hxembedded_window_hookup_ipc_callbacks(window);
                
            g_free(window);
            
            windows_iter = g_list_next(windows_iter);
        }
        g_list_free(console->windows_list);        
        
        g_free(console);
        
        consoles_iter = g_list_next(consoles_iter);
    }
    g_list_free(g_hxembedded_app.consoles_list);
}

