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

/* Helix includes */
#include "hxcom.h"
#include "hxwintyp.h"
#include "hxstring.h"
#include "hxurl.h"

#include "commonapp.h"
#include "mainapp.h"
#include "auth.h"
#include "error.h"
#include "upgrade.h"
#include "remote.h"
#include "switchboard.h"

#include "appver.h"

#include "hlxclib/string.h"
#include "hlxclib/stdio.h"
#include "hlxclib/stdlib.h"
#include "hlxclib/stdarg.h"
#include "hlxclib/getopt.h"
#include "hlxclib/ctype.h"

#ifdef G_OS_UNIX
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "embeddedapp.h"
#endif

#include <glib.h>

#include "winutils.h"
#include "hxplayer.h"
#include "hxstatisticsobserver.h"
#include "hxgvalue.h"
#include "hxgprefs.h"
#include "hxgerror.h"

#include "favorites.h"

#include <unistd.h>
#include <locale.h>
#include "hxplayer-i18n.h"

#define EXIT_RESTART_PLAYER 10
#define MAX_TITLE_LENGTH 60

HXCommonApp g_hxcommon_app;

/* hxcommon_* functions   
 * ====================
 * Utility functions that perform operations common to both the embedded
 * and top-level player.
 */

/* WARNING! This function will modify the string it is passed */
gchar*
hxcommon_strtrim(gchar* str)
{
    gchar* start = str;
    gchar* end;
    gchar* mark = NULL;

    /* Find the first non-space character */
    while(*start && isspace(*start))
    {
        start++;
    }

    end = start;

    /* trim off any trailing whitespace */
    while(*end)
    {
        if(*end && !isspace(*end))
        {
            mark = end;
        }
        end++;
    }

    if(mark)
    {
        /* Null terminate after the last non-space character */
        mark++;
        *mark = '\0';
    }
    
    return start;
}

gchar*
hxcommon_strdup_trim(const gchar* str)
{
    const gchar* start = str;
    gchar* end;
    gchar* str_out;
    gchar* mark = NULL;

    /* Find the first non-space character */
    while(*start && isspace(*start))
    {
        start++;
    }

    str_out = g_strdup(start);
    end = str_out;

    /* trim off any trailing whitespace */
    while(*end)
    {
        if(*end && !isspace(*end))
        {
            mark = end;
        }
        end++;
    }

    if(mark)
    {
        /* Null terminate after the last non-space character */
        mark++;
        *mark = '\0';
    }
    
    return str_out;
}


gchar*
hxcommon_escape_underscores(const char* str)
{
    gint buf_size = 0;
    gchar* buf;
    const gchar* src_pos;
    gchar* dest_pos;
    
    /* Pass 1: count the underscores */
    src_pos = str;
    while(*src_pos)
    {
        if(*src_pos == '_')
        {
            buf_size += 2;
        }
        else
        {
            buf_size++;
        }
        src_pos++;
    }

    buf_size++;
    
    /* Pass 2: Allocate and copy */
    buf = (gchar*)g_try_malloc(buf_size);
    g_return_val_if_fail(buf != NULL, NULL);
    src_pos = str;
    dest_pos = buf;
    while(*src_pos)
    {
        if(*src_pos == '_')
        {
            /* Add an extra underscore */
            *dest_pos++ = '_';
        }
        *dest_pos++ = *src_pos++;
    }

    *dest_pos = '\0';

    return buf;
}

gchar*
hxcommon_get_title_from_url(const gchar* url)
{
    const gchar* pos;
    gchar* p;
    gint len = 0;
    gchar* title;
    const gchar* slash_pos = NULL;
    guint i;

    pos = url;
    while(*pos)
    {
        if(*pos == '/')
        {
            slash_pos = pos;
        }
        else if(*pos == '?')
        {
            break;
        }
        pos++;
    }

    if(slash_pos)
    {
        len = pos - slash_pos;
    }
    else
    {
        len = pos - url - 1;
    }
    
    if(!slash_pos || len == 0)
    {
        title = g_strdup(_("untitled"));
    }
    else
    {
        /* Decode the title */
        gchar* encoded_filename;
        gchar* filename;
        gchar c;

        encoded_filename = (gchar*) g_malloc(len + 1);
        strncpy(encoded_filename, slash_pos + 1, len);
        encoded_filename[len] = '\0';

        filename = hxcommon_strdup_and_unescape_url(encoded_filename);
        g_free(encoded_filename);

        /* Filename is in utf-8 at this point. Truncate if necessary. */
        c = g_utf8_get_char (filename);
        p = filename;
        i = 0;
        while(c)
        {
            p = g_utf8_next_char(p);
            c = g_utf8_get_char (p);
            i++;
            if(i == MAX_TITLE_LENGTH)
            {
                /* Back up 3 characters */
                p = g_utf8_prev_char(p);
                p = g_utf8_prev_char(p);
                p = g_utf8_prev_char(p);

                /* XXXRGG: Not sure if this is valid for all languages... */
                *p++ = '.';
                *p++ = '.';
                *p++ = '.';
                *p++ = '\0';

                /* Trim the buffer */
                filename = (gchar*) g_realloc(filename, p - filename);
                
                break;
            }
        }

        title = filename;
    }

    return title;
}

/* Caller must free the returned string */
gchar*
hxcommon_locate_file(const gchar *file_name)
{
#ifdef USE_GNOME
    /* prepend the package prefix */
    gchar* result;
    gchar* package_file_name = g_strdup_printf("%s/%s", g_hxcommon_app.package, file_name);
    
    result = gnome_program_locate_file(NULL,
                                       GNOME_FILE_DOMAIN_APP_DATADIR,
                                       package_file_name,
                                       FALSE,
                                       NULL);
    g_free(package_file_name);

    return result;
#else
    guint i;
    gchar* resolved_path = NULL;
    gchar* resource_paths[] =
    {
        "",
        "/" APP_NAME_SHORT,
        "/default"
    };

#ifndef HELIX_FEATURE_LIBGLADE
    /* Ignore requests for .glade files -- we don't need them */
    if(strstr(file_name, ".glade"))
    {
        return g_strdup(file_name);
    }
#endif
    
    for(i = 0; i < sizeof(resource_paths) / sizeof(*resource_paths); i++)
    {
        resolved_path = g_strdup_printf("%s/%s%s/%s",
                                        g_hxcommon_app.package_data_dir,
                                        g_hxcommon_app.package,
                                        resource_paths[i],
                                        file_name);
        
        if(g_file_test(resolved_path, G_FILE_TEST_EXISTS))
        {
            break;
        }

        g_free(resolved_path);
        resolved_path = NULL;
    }

    return resolved_path;
#endif
}

gboolean
hxcommon_url_show(const gchar* orig_url)
{
    gboolean ret = FALSE;
    gchar* argv[3];
    gchar* url;

    url = g_strdup(orig_url);
    argv[1] = url;
    argv[2] = NULL;
    
    if(g_hxcommon_app.web_browser_command)
    {
        argv[0] = g_hxcommon_app.web_browser_command;

        /* Use the user-specified browser */
        ret = g_spawn_async (NULL, /* working directory */
                             argv,
                             NULL, /* envp */
                             G_SPAWN_SEARCH_PATH, /* flags */
                             NULL,  /* child_setup */
                             NULL,  /* data */
                             NULL,  /* child_pid */
                             NULL);
        
    }

    if(!ret)
    {        
#ifdef USE_GNOME
        GError *error = NULL;
        gnome_url_show(url, &error);

        if (error != NULL)
        {
            g_warning (error->message);	
            g_error_free (error);
        }
#else
        /* try some of the usual suspects */
        guint i;
        gchar* browser_commands[] = {
            "htmlview",       /* RedHat */
            "x-www-browser",  /* Debian */
            "firefox",
            "MozillaFirebird",
            "mozilla",   
            "opera",
            "epiphany",
            "konqueror",
            "nautilus",
            "galeon",
            "iexplore"        /* Windows */
        };
        
        for(i = 0; i < sizeof(browser_commands) / sizeof(*browser_commands); i++)
        {
            argv[0] = browser_commands[i];

            ret = g_spawn_async (NULL, /* working directory */
                                 argv,
                                 NULL, /* envp */
                                 G_SPAWN_SEARCH_PATH, /* flags */
                                 NULL,  /* child_setup */
                                 NULL,  /* data */
                                 NULL,  /* child_pid */
                                 NULL);
            if(ret)
            {
                g_hxcommon_app.web_browser_command = g_strdup(browser_commands[i]);
                break;
            }
        }
    }    
#endif /* USE_GNOME */

    if(!ret)
    {
        GtkWidget* dialog;

        dialog = gtk_message_dialog_new(NULL,
                                        GTK_DIALOG_MODAL,
                                        GTK_MESSAGE_ERROR,
                                        GTK_BUTTONS_OK,
                                        _("Failed to launch a web browser for help"));

        gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);

        gtk_dialog_run (GTK_DIALOG (dialog));
        gtk_widget_destroy (dialog);        
    }

    g_free(url);

    return ret;    
}

void
hxcommon_close_dialog(GtkWidget* dialog,
                      gint response,
                      gpointer* data_ptr)
{
    if(response != GTK_RESPONSE_HELP &&
       response != GTK_RESPONSE_APPLY)
    {
        gtk_widget_destroy(dialog);
        *data_ptr = NULL;
    }
}

/* We do transient setting this way so we can pop up this dialog
   from the embedded player, where we don't have a GtkWindow to
   work with. */
void
hxcommon_embedded_transient_parent_realized (GtkWidget *dialog,
                                             GdkWindow *parent)
{
    if (GTK_WIDGET_REALIZED (dialog))
    {
        gdk_window_set_transient_for (dialog->window, parent);
    }
}


void
hxcommon_launch_error_help_with_widget_name(GtkWidget* widget)
{
    gchar* url;
    GtkWidget* toplevel;
    guint error_code;

    toplevel = hxcommon_get_toplevel_widget_no_menus(widget);

    error_code = (guint)g_object_get_data(G_OBJECT(toplevel), "error_code");
        
    url = hx_switchboard_get_error_help_url(error_code);

    hxcommon_url_show(url);
    g_free(url);    
}

void
hxcommon_launch_upgrade_with_widget_name(GtkWidget* widget)
{
    gchar* url;
    GtkWidget* toplevel;
    gchar* components;

    toplevel = hxcommon_get_toplevel_widget_no_menus(widget);

    components = (gchar*)g_object_get_data(G_OBJECT(toplevel), "components");

    url = hx_switchboard_get_upgrade_url (components);
    
    hxcommon_url_show(url);
    g_free(url);    
}


void
hxcommon_launch_context_help(const gchar* context)
{
    gchar* url;
    url = hx_switchboard_get_context_help_url(context);
    hxcommon_url_show(url);
    g_free(url);
}

void
hxcommon_launch_context_help_with_widget_name(GtkWidget* widget)
{
    /* Glade's handling of user data leaves much to be desired, so
       we use the widget name as the context we pass to the wiki. */
    const gchar* context;

    context = gtk_widget_get_name(widget);
    g_return_if_fail(context != NULL);

    hxcommon_launch_context_help(context);
}   

GtkWidget*
hxcommon_get_toplevel_widget_no_menus(GtkWidget* widget)
{
    GtkWidget* parent;

    /* Get the toplevel widget, bypassing menu toplevels */
    for(;;)
    {
        if(GTK_IS_MENU(widget))
        {
            parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
        }
        else
        {
            parent = widget->parent;
        }

        if(parent == NULL)
        {
            break;
        }
        widget = parent;
    }

    return widget;
}

gboolean
hxcommon_player_has_feature (GtkWidget*   /* player */,
                             const gchar* feature)
{
    gboolean has_feature = FALSE;
    
#ifdef HELIX_FEATURE_REAL_BRANDING
    /* If we're branded as a RealPlayer, claim compatibility with RealOne,
       as authoring often uses this to determine whether or not a player
       can play SMIL2.0. For Helix Player, do the right thing and don't
       claim support for RealOne. */

    /* http://features.real.com/? */
    gchar feature_player[] = "component;player=";
    gint  feature_player_len = sizeof(feature_player) - 1;
    
    if(strncmp(feature, feature_player, feature_player_len) == 0)
    {
        int version[3];

        sscanf(feature + feature_player_len,
               "%d.%d.%d",
               &version[0],
               &version[1],
               &version[2]);

        /* We only match version 6.0.10 exactly, as our intention is only
           to pass the SMIL1.0/SMIL2.0 test authoring uses. */
        if(version[0] == 6 && version[1] == 0 && version[2] == 10)
        {
            has_feature = TRUE;
        }
    }
    else
    {
        g_warning("Unhandled request for feature %s", feature);
    }
#endif

    return has_feature;
}

void
hxcommon_run_error_dialog(GtkWindow*   win_parent,
                          GdkWindow*   embedded_parent,
                          guint        hxcode,
                          guint        user_code,
                          const gchar* error_string,
                          const gchar* user_string,
                          const gchar* more_info_url)
{
    GtkDialog* dialog;
    gint response;

    g_return_if_fail(win_parent != NULL || embedded_parent != NULL);

    dialog = hxplay_error_dialog_new(hxcode,
                                     user_code,
                                     error_string,
                                     user_string,
                                     more_info_url);

    g_return_if_fail(dialog != NULL);

    if(win_parent)
    {
        gtk_window_set_transient_for(GTK_WINDOW(dialog),
                                     GTK_WINDOW(win_parent));
    }    
    else if(embedded_parent)
    {        
        /* We do transient setting this way so we can pop up this dialog
           from the embedded player, where we don't have a GtkWindow to
           work with. */
        g_signal_connect (G_OBJECT (dialog), "realize",
                          G_CALLBACK (hxcommon_embedded_transient_parent_realized),
                          embedded_parent);
    }

    do
    {
        response = gtk_dialog_run (GTK_DIALOG (dialog));
    } while(response == GTK_RESPONSE_HELP);

    gtk_widget_destroy(GTK_WIDGET(dialog));    
}

void
hxcommon_run_request_upgrade_dialog(GtkWindow* win_parent,
                                    GdkWindow* embedded_parent,
                                    const gchar* url,
                                    GList* components_list,
                                    gboolean blocking)
{
    GtkDialog* dialog;
    gint response;

    g_return_if_fail(win_parent != NULL || embedded_parent != NULL);

    dialog = hxplay_request_upgrade_dialog_new (url,
                                                components_list,
                                                blocking);

    g_return_if_fail(dialog != NULL);

    if(win_parent)
    {
        gtk_window_set_transient_for(GTK_WINDOW(dialog),
                                     GTK_WINDOW(win_parent));
    }    
    else if(embedded_parent)
    {        
        /* We do transient setting this way so we can pop up this dialog
           from the embedded player, where we don't have a GtkWindow to
           work with. */
        g_signal_connect (G_OBJECT (dialog), "realize",
                          G_CALLBACK (hxcommon_embedded_transient_parent_realized),
                          embedded_parent);
    }

    do
    {
        response = gtk_dialog_run (GTK_DIALOG (dialog));
    } while(response == GTK_RESPONSE_HELP);

    gtk_widget_destroy(GTK_WIDGET(dialog));    
}


void
hxcommon_run_authentication_dialog (GtkWindow* win_parent,
                                    GdkWindow* embedded_parent,
                                    HXPlayer*    player,
                                    const gchar* server_proxy,
                                    const gchar* realm,
                                    gboolean     is_proxy_server)
{
    /* We run authentication as a modal dialog box. */
    GtkDialog* dialog;
    const gchar* str;
    gchar* username = NULL;
    gchar* password = NULL;
    gint response;

    g_return_if_fail(win_parent != NULL || embedded_parent != NULL);
    
    dialog = hxplay_authentication_dialog_new(player,
                                              server_proxy,
                                              realm,
                                              is_proxy_server);
    g_return_if_fail(dialog != NULL);

    if(win_parent)
    {
        gtk_window_set_transient_for(GTK_WINDOW(dialog),
                                     GTK_WINDOW(win_parent));
    }    
    else if(embedded_parent)
    {        
        /* We do transient setting this way so we can pop up this dialog
           from the embedded player, where we don't have a GtkWindow to
           work with. */
        g_signal_connect (G_OBJECT (dialog), "realize",
                          G_CALLBACK (hxcommon_embedded_transient_parent_realized),
                          embedded_parent);
    }

    do
    {
        response = gtk_dialog_run (GTK_DIALOG (dialog));
    } while(response == GTK_RESPONSE_HELP);

    if(response == GTK_RESPONSE_OK)
    {

        str = hxplay_authentication_dialog_get_username(dialog);
        if(str)
        {
            username = g_strdup(str);
        }
        
        str = hxplay_authentication_dialog_get_password(dialog);

        if(str)
        {
            password = g_strdup(str);
        }
    }

    gtk_widget_destroy(GTK_WIDGET(dialog));
    
    if(username && password)
    {
        hx_player_authenticate(player, TRUE, username, password);
    }

    if(username)
    {
        g_free(username);
    }
    
    if(password)
    {
        g_free(password);
    }
}

#ifdef HELIX_FEATURE_RP_SIGN_IN
void
hxcommon_run_signin_dialog(GtkWindow* parent,
                           HXPlayer* player)
{
    /* We run authentication as a modal dialog box. */
    GtkDialog* dialog;
    const gchar* str;
    gchar* username = NULL;
    gchar* password = NULL;
    gint response;

    dialog = hxplay_sign_in_dialog_new(player);
    g_return_if_fail(dialog != NULL);

    gtk_window_set_transient_for(GTK_WINDOW(dialog),
                                 parent);

    do
    {
        response = gtk_dialog_run (GTK_DIALOG (dialog));
    } while(response == GTK_RESPONSE_HELP);

    if(response == GTK_RESPONSE_OK)
    {
        str = hxplay_sign_in_dialog_get_username(dialog);
        if(str)
        {
            username = g_strdup(str);
        }
        
        str = hxplay_sign_in_dialog_get_password(dialog);

        if(str)
        {
            password = g_strdup(str);
        }
    }

    gtk_widget_destroy(GTK_WIDGET(dialog));
    
    if(username && password)
    {
        const char* pParams = NULL;
        const char* pSwitchboardParams = NULL;
        const char* pFormattedParams = ""; //"&language=%@&country=%@";
        INT32 nSessionIdIn = 0;
        INT32 nSessionIdOut = 0;
        
        retVal = g_pR1ServiceAuthentication->AuthenticateUser( username,
                                                               password,
                                                               nSessionIdIn,
                                                               pParams,
                                                               pSwitchboardParams,
                                                               &nSessionIdOut,
                                                               pFormattedParams );
        if(FAILED(retVal))
        {
            g_warning("sign-in failed");
        }                   
    }

    if(username)
    {
        g_free(username);
    }
    
    if(password)
    {
        g_free(password);
    }
}
#endif

/* convenience wrapper around g_io_channel_write_chars */
gboolean
hxcommon_channel_write(GIOChannel* chan, const gchar* data)
{
    GIOStatus status;
    GError* error = NULL;
    gsize bytes_written;
    gsize len;

    len = strlen(data);
    status = g_io_channel_write_chars(chan, data, len, &bytes_written, &error);

    if(error)
    {
        g_warning(error->message);
        g_error_free(error);
        return FALSE;
    }
    if(status != G_IO_STATUS_NORMAL || len != bytes_written)
    {
        g_warning("Error writing config file");
        return FALSE;
    }
        
    return TRUE;
} 

static gchar*
strdup_and_unescape(const gchar* src_str)
{
    /* This is what the unix prefs do */
    gchar* dest_str;
    gchar* dest_pos;
    const gchar* src_pos;
    gint buffer_length = 0;

    if(!src_str)
    {
        return NULL;
    }

    /* Calculate buffer length */
    src_pos = src_str;
    while(*src_pos)
    {
        if(*src_pos == '%')
        {
            src_pos += 3;
        }
        else
        {
            src_pos++;
        }
        buffer_length++;
    }
    
    dest_str = (gchar*)g_malloc(buffer_length + 1);
    
    src_pos = src_str;
    dest_pos = dest_str;
    while(*src_pos)
    {
        if(*src_pos != '%')
        {
            *dest_pos++ = *src_pos++;
        }
        else
        {
            int c = ' ';
            sscanf(src_pos, "%02x", &c);
            *dest_pos++ = (gchar)c;
            src_pos += 3;
        }
    }

    *dest_pos = '\0';
    
    return dest_str;    
}

static gboolean
is_special_character(gchar c)
{
    gboolean is_special = FALSE;
    
    is_special = (c == '%' || c == '\n' || c == '=');

    return is_special;
}

static gchar*
strdup_and_escape(const gchar* src_str)
{
    /* This is what the unix prefs do */
    gchar* dest_str;
    gchar* dest_pos;
    const gchar* src_pos;
    gint buffer_length = 0;

    if(!src_str)
    {
        return NULL;
    }

    /* Calculate buffer length */
    src_pos = src_str;
    while(*src_pos)
    {
        if(is_special_character(*src_pos))
        {
            buffer_length += 3;
        }
        else
        {
            buffer_length++;
        }
        src_pos++;
    }
    
    dest_str = (gchar*)g_malloc(buffer_length + 1);

    
    src_pos = src_str;
    dest_pos = dest_str;
    while(*src_pos)
    {
        if(is_special_character(*src_pos))
        {
            /* These characters are also escaped by the real prefs code
               in common/util/platform/unix/unix_pref.cpp */
        
            dest_pos += sprintf(dest_pos, "%%%02x", *src_pos);
            src_pos++;
        }
        else
        {
            *dest_pos++ = *src_pos++;
        }        
    }

    *dest_pos = '\0';

    return dest_str;
}

gchar*
hxcommon_strdup_and_escape_url(const gchar* url)
{
    CHXString strEncodedUrl;
    const char* encoded_url;
    HX_RESULT result;

    result = CHXURL::encodeURL(url, strEncodedUrl);
    g_return_val_if_fail(SUCCEEDED(result), NULL);

    encoded_url = (const char*) strEncodedUrl;
    
    return g_strdup(encoded_url);
}

gchar*
hxcommon_strdup_and_unescape_url(const gchar* url)
{
    CHXString strDecodedUrl;
    const char* decoded_url;
    HX_RESULT result;

    result = CHXURL::decodeURL(url, strDecodedUrl);
    g_return_val_if_fail(SUCCEEDED(result), NULL);

    decoded_url = (const char*) strDecodedUrl;
    
    return g_strdup(decoded_url);
}

void
hxcommon_save_preferences(HXMainWindow* window)
{
    GIOChannel* chan;
    const gchar* home;
    gchar* filename;
    GError* error = NULL;
    gchar* line;
    GSList* prefs_iter;
    gboolean result;

    /* XXXRGG: For now, don't support saving prefs from the embedded player */
    g_return_if_fail(window != NULL);

    if(!g_hxcommon_app.save_preferences)
    {
        /* Right now, this means that we're doing a player reset */
        return;
    }
    
    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, "w", &error);
    g_free(filename);
    if(error)
    {
        if(error->code != G_FILE_ERROR_NOENT)
        {
            /* If the file doesn't exist, this may be the
               first time we've run */
            g_warning(error->message);
        }
        g_error_free(error);
        error = NULL;
        return;
    }

    if(!chan)
    {
        /* File can't be created */
        return;
    }

    /* Step 1: Write out all the helix engine preferences */

    hxcommon_channel_write(chan, "[helix]\n");

    prefs_iter = hx_prefs_get_all_entries();
    while(prefs_iter)
    {
        HXValue* value = NULL;
        HXEntry* entry = NULL;
        gchar* escaped_value_str;
        
        entry = (HXEntry*)prefs_iter->data;
        value = hx_entry_get_value(entry);

        escaped_value_str = strdup_and_escape(hx_value_get_string(value));
        line = g_strdup_printf("%s=%s\n", hx_entry_get_key(entry), escaped_value_str);
        g_free(escaped_value_str);

        result = hxcommon_channel_write(chan, line);        
        g_free(line);

        g_return_if_fail(result);
        
        prefs_iter = g_slist_next(prefs_iter);
    }

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

void
hxcommon_load_preferences(HXMainWindow* window)
{
    GIOChannel* chan;
    const gchar* home;
    gchar* filename;
    GIOStatus status;
    GError* error = NULL;
    gchar* line;
    HXValue* value;
    gboolean override_open_audio_pref = TRUE;
    gchar* favorite_title = NULL;
    gint favorite_index_title = -1;
    
    enum SectionNameType
    {
        HELIX_SECTION = 0,
        RECENT_URLS_SECTION = 1,
        PLAYER_SECTION = 2,
        FAVORITES_SECTION = 3,
        SIGN_IN_SECTION = 4
    } section_name;
    
    const gchar* section_labels[] =
    {
        "helix",
        "recent_urls",
        "player",
        "favorites",
        "signin"
    };

    // If no section name, assume helix section
    section_name = HELIX_SECTION; 
    
    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);
    g_free(filename);
    if(error)
    {
        if(error->code != G_FILE_ERROR_NOENT)
        {            
            g_warning(error->message);
        }
        g_error_free(error);
        error = NULL;
        return;
    }

    if(!chan)
    {
        /* File does not exist */
        return;
    }

#ifdef G_OS_UNIX
    /* Set permissions to readable/writable only by user */
    fchmod(g_io_channel_unix_get_fd(chan), S_IREAD | S_IWRITE);
#endif
    
    /* Force line termination to '\n' only. Otherwise, the '\r' in some
       of the plugin handler preferences confuse us. */
    g_io_channel_set_line_term(chan, "\n", 1);
    
    for(;;)
    {
        status = g_io_channel_read_line(chan, &line, NULL, NULL, &error);
        if(error)
        {
            g_warning(error->message);
            g_error_free(error);
            error = NULL;
            break;
        }
        if(status == G_IO_STATUS_EOF)
        {
            break;           
        }
        else if(status == G_IO_STATUS_AGAIN || !line)
        {
            continue;
        }
        else if(status != G_IO_STATUS_NORMAL)
        {
            g_warning("Error reading config file");
            break;
        }

        if(line[0] == '[')
        {
            gchar* section;
            gchar* pos;
            
            /* We have a new section */
            if((pos = strchr(line, ']')))
            {
                guint i;
                *pos = '\0';
                section = line + 1;

                section_name = HELIX_SECTION; // default to helix
                
                for(i = 0; i < sizeof(section_labels) / sizeof(*section_labels); i++)
                {
                    if(strcmp(section, section_labels[i]) == 0)
                    {
                        section_name = (enum SectionNameType)i;
                        break;
                    }
                }                
            }       
        }
        else
        {        
            // Parse the line
            gchar* key = line;
            gchar* value_str = strchr(line, '=');
            
            if(key && value_str)
            {
                gchar* pos;
                gchar* unescaped_value_str = NULL;

                // terminate the key
                *value_str++ = '\0';

                // get rid of the trailing newline on the value
                pos = strchr(value_str, '\n');
                if(pos)
                {
                    *pos = '\0';
                }
                
                switch(section_name)
                {
                    case HELIX_SECTION:
                        /* Add it to the player */
                        unescaped_value_str = strdup_and_unescape(value_str);
                        value = hx_value_new(HX_VALUE_STRING);
                        hx_value_set_string(value, unescaped_value_str);
                        hx_prefs_set_entry(key, value);
                        hx_value_free(value);
                        g_free(unescaped_value_str);

                        if(strcmp(key, "OpenAudioDeviceOnPlayback") == 0)
                        {
                            override_open_audio_pref = FALSE;
                        }
                        break;
                        
                    case RECENT_URLS_SECTION:
                        if(window)
                        {
                            if(strncmp(key, "url", 3) == 0)
                            {
                                hxwindow_recent_urls_append(window, value_str);
                            }
                        }
                        break;
                        
                    case PLAYER_SECTION:
                        if(window)
                        {
                            GValue value;
                            memset(&value, 0, sizeof(value));
                            g_value_init(&value, G_TYPE_STRING);
                            g_value_set_string(&value, value_str);
                            hxwindow_set_property(window, key, &value);
                            g_value_unset(&value);
                        }
                        break;

                    case FAVORITES_SECTION:
                        if(window)
                        {
                            const gint favorite_title_len = sizeof("favorite_title") - 1;
                            const gint favorite_url_len = sizeof("favorite_url") - 1;
                            
                            if(strncmp(key, "favorite_title", favorite_title_len) == 0)
                            {
                                gchar* pos;
                                pos = key + favorite_title_len;
                                favorite_index_title = -1;
                                sscanf(pos, "%d", &favorite_index_title);

                                g_free(favorite_title);
                                favorite_title = g_strdup(value_str);
                            }
                            else if(strncmp(key, "favorite_url", sizeof("favorite_url") - 1) == 0)
                            {
                                gint favorite_index_url = -1;
                                gchar* pos;
                                pos = key + favorite_url_len;
                                sscanf(pos, "%d", &favorite_index_url);
                                if(favorite_title && favorite_index_url == favorite_index_title)
                                {
                                    hxwindow_favorite_add(window,
                                                          favorite_title,
                                                          value_str);

                                    g_free(favorite_title);
                                    favorite_title = NULL;
                                    favorite_index_title = -1;
                                    favorite_index_url = -1;
                                }
                                else
                                {
                                    g_warning("Parse error reading favorites");                                    
                                }
                            }
                            else
                            {
                                g_warning("Parse error reading favorites");
                            }
                        }
                        break;
#ifdef HELIX_FEATURE_RP_SIGN_IN
                    case SIGN_IN_SECTION:
                        if(window)
                        {
                            hxwindow_set_sign_in_property(window, key, value_str);
                        }
                        break;                        
#endif                        
                    default:
                        break;
                }
            }
        }        
        g_free(line);
    }

    g_io_channel_shutdown(chan, TRUE, &error);

    if(override_open_audio_pref)
    {
        /* Open the audio device on startup instead of playback. This
           gives us the ability to read the volume properly on startup */
        value = hx_value_new(HX_VALUE_STRING);
        hx_value_set_string(value, "0");
        hx_prefs_set_entry("OpenAudioDeviceOnPlayback", value);
        hx_value_free(value);        
    }
}

void
hxcommon_reset_player(void)
{
    gchar* filename;
    const gchar* home;

    gtk_main_quit(); // Request that we quit

    g_hxcommon_app.restart_on_quit = TRUE;
    g_hxcommon_app.save_preferences = FALSE;
    
    home = g_get_home_dir();
    filename = g_strdup_printf("%s/%s", home, HXPLAYER_RC);    

    unlink(filename);

    g_free(filename);
}

/* main
 * ====
 * These functions support the setup of the app in main();
 */
static void
parse_command_line(HXCommandLineOptions* cmd, int* pargc, char*** pargv)
{
    /* XXXRGG: This function will mix up the order of arguments.
       This could conceivably be a problem with some options... */
    int c;
    int option_index = 0;
    char** argv;
    int argc;
    GList* unknown_options = NULL;
    GList* iter;

    // XXXRGG: gdb resolves &optind incorrectly. Use a pointer to look
    // at its value (gdb 5.3.90_2003-08-01-cvs-debian):
    // int* _optind = &optind;

#ifdef _GNU_SOURCE
    /* suppress errors */
    opterr = 0;
#endif
    
    argv = *pargv;
    argc = *pargc;
    
    /* Command line options */
    memset(cmd, 0, sizeof(*cmd));
    cmd->run_embedded = FALSE;
    cmd->command_fd = -1;
    cmd->callbacks_fd = -1;
    cmd->is_remote_command = FALSE;
    cmd->fullscreen = FALSE;
    cmd->quit_on_done = FALSE;

    // Location is not set in this function. We set it once gtk
    // and glib have swallowed the args they want from the command line.
    cmd->location = NULL; 

    for(;;)
    {
#ifdef _GNU_SOURCE
	static struct option long_options[] = {
	    /* name          has_arg  flag    val */
	    {  "embedded",   1,       0,      'e' }, // argument is xid
            {  "callbacks",  1,       0,      'c' },
	    {  "remote",     0,       0,      'r' },
	    {  "help",       0,       0,      'h' },
            {  "fullscreen", 0,       0,      'f' },
            {  "quit",       0,       0,      'q' },
            {  "new",        0,       0,      'n' },
	    {  0,            0,       0,      0   },
	};
	
	c = getopt_long(argc, argv, "c:e:fhnqr",
			long_options, &option_index);
#else
	c = getopt(argc, argv, "c:e:hnr");
#endif
        
	if(c == -1)
	{
	    break;
	}

	switch(c)
	{
	    case ':':
		/* Missing a value for an option */
		break;
		
	    case 'r':
		/* tbd: implement remote commands */
		cmd->is_remote_command = TRUE;
		break;

	    case 'e':
                if(!sscanf(optarg, "%d", &cmd->command_fd))
                {
                    g_error(_("Invalid arguement to --embedded: %s"),
                              optarg);
                }
                cmd->run_embedded = TRUE;
                break;

            case 'c':
                if(!sscanf(optarg, "%d", &cmd->callbacks_fd))
                {
                    g_error(_("Invalid arguement to --callbacks: %s"),
                              optarg);
                }
		break;

	    case 'h':
		printf(_(
		    "Usage: hxwindow [OPTION]... [FILE]\n"
		    "\n"
		    "  -r, --remote        Send a running player a command\n"
		    "  -h, --help          Display this message\n"
		    "  -e, --embedded FD   Run player in embedded mode, receive commands on descriptor FD\n"
                    "  -c, --callbacks FD  Use with --embedded, receive callbacks on descriptor FD\n"
                    "  -f, --fullscreen    Run the player in fullscreen mode on startup\n"
                    "  -n, --new           Open in new player (do not reuse a running player\n"
                    "  -q, --quit          Quit when playback is completed (used in conjunction with a URL\n"
		    "\n"
		    "https://player.helixcommunity.org\n"
		    ));
		exit(EXIT_SUCCESS);
		break;

            case 'f':
		cmd->fullscreen = TRUE;
                break;

            case 'n':
                cmd->open_in_new_player = TRUE;
                break;
                
            case 'q':
		cmd->quit_on_done = TRUE;                
                break;

            case '?':
            {
                gchar* option = argv[optind - 1];
                unknown_options = g_list_append(unknown_options, option);

                if(optind < argc)
                {
                    if(argv[optind][0] != '-')
                    {
                        /* Looks like this option has an argument with it.
                           Add it to our list so that they stay in order*/
                        optind++;
                        option = argv[optind - 1];
                        unknown_options = g_list_append(unknown_options, option);
                    }
                }                
                break;
            }   
	    default:
		g_assert(FALSE);
		break;
	}
    }


    /* Update argv/c to point to the remaining options */
    argc = 1;

    iter = unknown_options;
    while(iter)
    {
        argv[argc++] = (char*)iter->data;
        iter = g_list_next(iter);
    }

    while(optind < *pargc)
    {
        argv[argc++] = argv[optind++];
    }
    
    *pargc = argc;

    g_list_free(unknown_options);
}

static gboolean
locate_resources(const char *argv0)
{
    /* Find our resources */
    gboolean found = FALSE;
    gchar* test_file = APP_NAME_SHORT "/logo.png";
    gchar* resolved_argv0 = NULL;

    /* The following if's can be rearranged to change the desired
       search order */

    if(!found)
    {
        /* Try $HELIX_LIBS/share */
        char* helix_libs = getenv("HELIX_LIBS");
        if(helix_libs)
        {
            gchar* test_file_path = g_strdup_printf("%s/share/%s", helix_libs, test_file);

            if(g_file_test(test_file_path, G_FILE_TEST_EXISTS))
            {
                g_hxcommon_app.package_data_dir = g_strdup(helix_libs);
                g_hxcommon_app.package = g_strdup("share");
                found = TRUE;
            }

            g_free(test_file_path);
        }
    }
    
    if(!found)
    {
        /* Try the path provided at compilation time */
        gchar* package_path = g_strdup_printf("%s/%s/%s", PACKAGE_DATA_DIR, PACKAGE, test_file);

        if(g_file_test(package_path, G_FILE_TEST_EXISTS))
        {
            g_hxcommon_app.package_data_dir = g_strdup(PACKAGE_DATA_DIR);
            g_hxcommon_app.package = g_strdup(PACKAGE);
            found = TRUE;
        }
        g_free(package_path);        
    }

    if(!found)
    {
        /* The following functions require argv0 to be an absolute path */
        if(g_path_is_absolute(argv0))
        {
            resolved_argv0 = g_strdup(argv0);
        }
        else
        {
            resolved_argv0 = g_find_program_in_path(argv0);
        }
    }
    
#ifdef G_OS_UNIX
    if(!found)
    {
        /* If argv[0] is a symbolic link, resolve it and try that directory */
        char* resolved_exec_filename;
        gchar* resolved_exec_path;
        gchar* resource_test_filename;

#ifdef _GNU_SOURCE
        /* canonicalize_file_name is more intelligent than realpath, so use
           it if it's available. */
        resolved_exec_filename = canonicalize_file_name(resolved_argv0);
#else
	char buf[PATH_MAX+1] = "";
        resolved_exec_filename = realpath(resolved_argv0, buf);
#endif
        if(resolved_exec_filename)
        {
            resolved_exec_path = dirname(resolved_exec_filename);
            resource_test_filename = g_strdup_printf("%s/share/%s", resolved_exec_path, test_file);

            if(g_file_test(resource_test_filename, G_FILE_TEST_EXISTS))
            {
                g_hxcommon_app.package_data_dir = g_strdup(resolved_exec_path);
                g_hxcommon_app.package = g_strdup("share");
                found = TRUE;
            }
            g_free(resource_test_filename);
#ifdef _GNU_SOURCE
            /* allocated by canonicalize_file_name */
            free(resolved_exec_filename);
#endif
        }
    }
#endif /* G_OS_UNIX */

    if(!found)
    {
        gchar* resource_test_filename;

        resource_test_filename = g_strdup_printf("share/%s", test_file);
                                                 
        /* Try the working directory */
        if(g_file_test(resource_test_filename, G_FILE_TEST_EXISTS))
        {
            g_hxcommon_app.package_data_dir = g_strdup(".");
            g_hxcommon_app.package = g_strdup("share");
            found = TRUE;
        }
        g_free(resource_test_filename);
    }

    if(!found)
    {
        /* Try using the path passed to us in argv[0] */        
        gchar* exec_filename = g_strdup(resolved_argv0);
        gchar* exec_path = g_path_get_dirname(exec_filename);
        gchar* resource_test_filename =
            g_strdup_printf("%s/share/%s", exec_path, test_file);

        if(g_file_test(resource_test_filename, G_FILE_TEST_EXISTS))
        {
            g_hxcommon_app.package_data_dir = g_strdup(exec_path);
            g_hxcommon_app.package = g_strdup("share");
            found = TRUE;
        }
        g_free(exec_filename);
        g_free(resource_test_filename);
    }
    
    if(resolved_argv0)
    {
        g_free(resolved_argv0);
    }

    return found;
}

/* Control the status text at the bottom of the player widget */
static void
set_buffering_status_text(HXPlayer* player,
                          HXBufferingReason /* reason */,
                          guint percent)
{
    gchar* text;

    if (percent == 100)
    {
	// blank the display when we hit 100
	hx_player_set_status_text(player, NULL);
    }
    else
    {
    text = g_strdup_printf("Buffering (%d%%)", percent);
    hx_player_set_status_text(player, text);
    }
}

static void
set_contacting_status_text(HXPlayer* player,
                           const gchar* contacting_text)
{
    if(!contacting_text)
    {
        contacting_text = _("Contacting server...");
    }
    hx_player_set_status_text(player, contacting_text);
}

static void
unset_status_text(HXPlayer* player)
{
    hx_player_set_status_text(player, NULL);    
}

void
hxcommon_show_status_in_player_widget(HXPlayer* player)
{
    g_signal_connect(G_OBJECT(player),
		     "buffering",
		     G_CALLBACK(set_buffering_status_text),
		     NULL);    

    g_signal_connect(G_OBJECT(player),
		     "contacting",
		     G_CALLBACK(set_contacting_status_text),
		     NULL);    

    g_signal_connect(G_OBJECT(player),
		     "play",
		     G_CALLBACK(unset_status_text),
		     NULL);

    g_signal_connect(G_OBJECT(player),
		     "pause",
		     G_CALLBACK(unset_status_text),
		     NULL);

    g_signal_connect(G_OBJECT(player),
		     "stop",
		     G_CALLBACK(unset_status_text),
		     NULL);
}

int
main(int argc, char *argv[]) 
{
    HXCommandLineOptions cmd;
    int i;
    gchar* version_error;
    gboolean result;

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

#ifdef G_OS_UNIX
    char* hxplayer_x_init_threads;

    /* Possible values are: normal, delayed, none */
    hxplayer_x_init_threads = getenv("HXPLAYER_X_INIT_THREADS");
    if(!hxplayer_x_init_threads)
    {
        hxplayer_x_init_threads = "after";
    }
#endif

#ifdef ENABLE_NLS
    /* Initialize gettext */
    setlocale (LC_ALL, "");
    bindtextdomain (PACKAGE, LOCALEDIR);

#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
    bind_textdomain_codeset(PACKAGE, "UTF-8");
#endif

    textdomain (PACKAGE);
#endif
    
#ifdef G_OS_UNIX
    if(strcmp(hxplayer_x_init_threads, "before") == 0)
    {
        Status status;
        status = XInitThreads();
        if(!status)
        {
            g_warning("X libraries appear to have been compiled without multithreaded support.");
        }
    }
#endif        

    /* Check the gtk version */
    version_error = gtk_check_version(GTK_MAJOR_VERSION,
                                      0,  /* GTK_MINOR_VERSION */
                                      0); /* GTK_MICRO_VERSION */
    if(version_error)
    {
        g_error("%s (expecting >= %d.%d.%d, using %d.%d.%d)",
                version_error,
                GTK_MAJOR_VERSION,
                0, /* GTK_MINOR_VERSION */
                0, /* GTK_MICRO_VERSION */
                gtk_major_version,
                gtk_minor_version,
                gtk_micro_version);
    }
    
    /* If we're running in embedded mode, close all
       open FD's we inherit, other than the command_fd,
       and std[in|out|err]. Inspired by bash. Also see fcloseall
       in glibc, though there are some fd's we want to keep open. */

    cmd.run_embedded = FALSE;

    /* Parse command line options */
    parse_command_line(&cmd, &argc, &argv);

#ifdef G_OS_UNIX
    int fd_table_size;
    if(cmd.run_embedded)
    {
	fd_table_size = getdtablesize ();
	if (fd_table_size > 1024)
	{
	    /* clamp to a reasonable value */
	    fd_table_size = 1024;
	}

        /* Start at 3 to skip stdout, stdin, stderr */
	for (i = 3; i < fd_table_size; i++)
	{
	    if((i != cmd.command_fd) && (i != cmd.callbacks_fd))
	    {
		close (i);
	    }
	}
    }
#endif
    
    winutils_screensaver_init();

    // This is a glib2.2 function
    // g_set_application_name(APP_NAME_FULL);

    if(!locate_resources(argv[0]))
    {
        g_error("Could not find resources\n");
    }
    
#ifdef USE_GNOME
    gnome_program_init("helixplayer", TARVER_STRING_VERSION,
		       LIBGNOME_MODULE,
		       argc, argv,
 		       // GNOME_PARAM_POPT_TABLE, options,
 		       GNOME_PARAM_HUMAN_READABLE_NAME, "Media Player",
 		       // GNOME_PROGRAM_STANDARD_PROPERTIES,
                       GNOME_PARAM_APP_PREFIX, PREFIX,
                       GNOME_PARAM_APP_SYSCONFDIR, SYSCONFDIR,
                       // GNOME_PARAM_APP_DATADIR, DATADIR,
                       GNOME_PARAM_APP_DATADIR, g_hxapp.package_data_dir,
                       GNOME_PARAM_APP_LIBDIR, LIBDIR
		       );
#endif

    if (!gtk_init_check(&argc, &argv))
    {
        /* g_error will call abort */
	g_error("Unable to open display");
    }    

#ifdef G_OS_UNIX
    if(strcmp(hxplayer_x_init_threads, "after") == 0)
    {
        Status status;
        status = XInitThreads();
        if(!status)
        {
            g_warning("X libraries appear to have been compiled without multithreaded support.");
        }
    }
#endif        

    glade_init();

    if(argc > 1)
    {
        cmd.location = strdup(argv[1]);
        if(argc > 2)
        {
            printf(_("Ignoring unknown options: "));
            for(i = 0; i < argc; i++)
            {
                printf("%s ", argv[optind++]);
            }
            printf("\n");
        }
    }

    g_hxcommon_app.is_embedded = cmd.run_embedded;
    g_hxcommon_app.save_preferences = TRUE;

    HXMainWindow* main_window = NULL;
    
    if(cmd.run_embedded)
    {
#ifdef HELIX_FEATURE_EMBEDDED_PLAYER
        // Use this if you're debugging a forked process
#if 0
#warning "Don't check in with loop active"
        printf("Attach to %d\n", getpid());
        int c = 0; 
        while(c == 0)
        {
        }
#endif
	if(!hxembedded_init(&cmd))
	{
	    return EXIT_FAILURE;
	}	
#endif /* HELIX_FEATURE_EMBEDDED_PLAYER */
    }
    else
    {
        gchar* url = NULL;
        
        if(cmd.location)
        {
            
            if(strstr(cmd.location, "://"))
            {
                url = g_strdup(cmd.location);
            }
            else
            {
                if(g_path_is_absolute(cmd.location))
                {
                    url = g_strdup_printf("file://%s", cmd.location);
                }
                else
                {
                    url = g_strdup_printf("file://%s/%s",
                                          g_get_current_dir(),
                                          cmd.location);
                }
            }
        }
        
        if (!cmd.open_in_new_player)
        {
            /* Attempt to open in an existing player first */
            const gchar* location = (url)? url: "";

            result = hxremote_launch_player (location);
            if(result)
            {
                exit(EXIT_SUCCESS);
            }
            /* else fall through and open up a new player */
        }
        
        main_window = hxwindow_new();
	if(!main_window)
	{
	    return EXIT_FAILURE;
	}

        if(cmd.location)
        {
            if(cmd.quit_on_done)
            {
                hxwindow_set_quit_on_done(main_window, TRUE);
            }
            if(cmd.fullscreen)
            {
                hxwindow_fullscreen(main_window);
            }

            hxwindow_open(main_window, url);
        }
        
        g_free(url);

        /* Get the web browser preference */
        GValue value;
        const gchar* value_str;

        memset(&value, 0, sizeof(value));
        
        result = hxwindow_get_property(main_window, "WebBrowserPath", &value);
        if(result)
        {
            value_str = g_value_get_string(&value);
            g_hxcommon_app.web_browser_command = g_strdup(value_str);
        }
    }

    gtk_main();

    if(cmd.run_embedded)
    {
#ifdef HELIX_FEATURE_EMBEDDED_PLAYER
        hxembedded_destroy();
#endif
    }
    
    g_free(g_hxcommon_app.package_data_dir);
    g_free(g_hxcommon_app.package);

    if(g_hxcommon_app.restart_on_quit)
    {
        return EXIT_RESTART_PLAYER;
    }
    else
    {
        return EXIT_SUCCESS;
    }
}

GdkPixmap*
hxcommon_get_logo_pixmap(GtkWidget* widget,
                         GdkWindow* win)
{
    GdkPixmap *pixmap = NULL;
    gchar *filename;
    GdkPixbuf *pixbuf = NULL;
    gint width, height;
    
    /* Set the splash pixmap */
    filename = hxcommon_locate_file("logo.png");
    if(filename)
    {
        pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
        g_free(filename);
    }

    if(pixbuf)
    {
        width = gdk_pixbuf_get_width(pixbuf);
        height = gdk_pixbuf_get_height(pixbuf);

        pixmap = gdk_pixmap_new(GDK_DRAWABLE(win),
                                width,
                                height,
                                -1);

        g_return_val_if_fail(pixmap != NULL, FALSE);

//         This is a gtk2.2 function. See below for the 2.0 version
//         gdk_draw_pixbuf(GDK_DRAWABLE(pixmap),
//                         widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
//                         pixbuf,
//                         0, 0, // src
//                         0, 0, // dest
//                         width, height,		    
//                         GDK_RGB_DITHER_NORMAL,
//                         0, 0  // dither
//                         );

        gdk_pixbuf_render_to_drawable(pixbuf,
                        GDK_DRAWABLE(pixmap),
                        widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
                        0, 0, // src
                        0, 0, // dest
                        width, height,		    
                        GDK_RGB_DITHER_NORMAL,
                        0, 0  // dither
                        );
    }

    return pixmap;
}


gchar*
hxcommon_get_rel_app_path(const gchar* path)
{
    const char* libs_path = getenv("HELIX_LIBS");
    if (libs_path)
    {
	if (path)
	{
	    return g_strdup_printf("%s/%s", libs_path, path);
	}
	return g_strdup(libs_path);
    }
    return g_strdup(path);
}

G_CONST_RETURN gchar*
hxcommon_get_distcode_language(void)
{
    static const struct
    {
        gchar* gettext_lang;
        gchar* distcode_lang;
    } lang_map[] =
    {
        { "C",     "EN" },
        { "en",    "EN" },
        { "de",    "DE" },
        { "es",    "ES" },
        { "fr",    "FR" },
        { "it",    "IT" },
        { "ja",    "JA" },
        { "ko",    "KO" },
        { "pt_BR", "PT" },
        { "zh_CN", "CN" },
        { "zh_TW", "TW" }
    };

    guint i;
    const gchar* lang = hxcommon_get_gettext_language();
    gchar* distcode_lang = NULL;

    if(lang)
    {
        for(i = 0; i < sizeof(lang_map) / sizeof(*lang_map); i++)
        {
            if(strncmp(lang, lang_map[i].gettext_lang, strlen(lang_map[i].gettext_lang)) == 0)
            {
                distcode_lang = lang_map[i].distcode_lang;
                break;
            }
        }
    }
    else
    {
        distcode_lang = "EN";
    }

    if(!distcode_lang)
    {
        distcode_lang = "XX";
    }
    
    return distcode_lang;
}


G_CONST_RETURN gchar*
hxcommon_get_gettext_language(void)
{
    const gchar *lang;

    // Priority LANGUAGE > LC_ALL > LC_MESSAGE > LANG > NULL
    lang = getenv ("LANGUAGE");
    if (lang == NULL || lang[0] == '\0')
    {
        lang = getenv ("LC_ALL");
        if (lang == NULL || lang[0] == '\0')
        {
            lang = getenv ("LC_MESSAGES");
            if (lang == NULL || lang[0] == '\0')
            {
                lang = getenv ("LANG");
                if (lang == NULL || lang[0] == '\0')
                    lang = NULL;
            }
        }
    }

    return lang;
}
