/*
 * Copyright (C) 2003-2017 the xine project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * gate to the spider monkey javascript interpreter
 *
 * provides functions to generate interpreter instances
 * customized for controlling the xine engine and the gxine
 * frontend. this is done by defining a set of global objects
 * and functions so any xine and gxine function can be invoked from
 * (java)scripts.
 *
 */

#include "globals.h"

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <stdarg.h>

#include <gdk/gdkkeysyms.h>
#if GTK_CHECK_VERSION(3, 0, 0)
#include <gdk/gdkkeysyms-compat.h>
#endif

#include "script_engine.h"
#include "ui.h"
#include "utils.h"
#include "mediamarks.h"
#include "playlist.h"
#include "settings.h"
#include "preferences.h"
#include "key_events.h"
#include "log_window.h"
#include "stream_info.h"
#include "open_mrl.h"
#include "player.h"
#include "server.h"
#include "wizards.h"
#include "snapshot.h"
#include "engine.h"
#include "history.h"

/*
#define LOG
*/
se_o_t *event_obj = NULL;

static se_o_t *js_icon_obj = NULL;

static JSRuntime *rt=NULL; /* global */

static GSList *se_chain;

static GtkListStore *se_error_store;
static GtkWidget *se_window, *eval_box;
static GtkTreeView *tree_view;

static const char *se_id (const se_t *se, const se_o_t *o, const char *prop)
{
  static char *id = NULL;

  free (id);
  id = prop ? strdup (prop) : NULL;
  while (o && o != se->g)
  {
    asreprintf (&id, "%s%s%s", o->id, id ? "." : "", id ? : "");
    o = o->parent;
  }
  return id ? : "[global]";
}

static void se_store_text (const char *type, const char *text)
{
  GtkTreeIter iter;
  GtkTreePath *path;
  gtk_list_store_append (GTK_LIST_STORE (se_error_store), &iter);
  gtk_list_store_set (GTK_LIST_STORE (se_error_store), &iter,
		      0, type, 1, text, -1);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (se_error_store), &iter);
  gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0, 1);
  gtk_tree_path_free (path);
}

static void se_js_icon (int flag)
{
  if (!gtk_widget_get_visible (se_window))
  {
    int oldv = se_prop_get_int (gse, js_icon_obj, "v");
    int newv = oldv | flag;
    if (oldv != newv)
      se_prop_set_int (gse, js_icon_obj, "v", newv);
  }
}

void se_result_cb (void *data, const char *format, ...)
{
  va_list ap;
  char *msg, *text;

  va_start (ap, format);
  msg = g_strdup_vprintf (format, ap);
  va_end (ap);
  text = g_markup_printf_escaped ("%s", msg);
  free (msg);
  se_store_text (GTK_STOCK_DIALOG_INFO, text);
  free (text);
  se_js_icon (1);
}

void se_error_cb (const char *msg, const JSErrorReport *report)
{
  static const char *const type[] = {
    GTK_STOCK_DIALOG_ERROR, GTK_STOCK_DIALOG_WARNING,
    GTK_STOCK_DIALOG_ERROR, GTK_STOCK_DIALOG_QUESTION
  };
  static const char flag[] = { 8, 4, 8, 2 };

  gchar *message = g_markup_printf_escaped ("<i>%s</i>", msg);
  ssize_t l = strlen (message) - 4;
  while (--l >= 0 &&
	 (message[l] == ' ' || message[l] == '\n' || message[l] == '\t'))
    /**/;
  strcpy (message + l + 1, "</i>"); /* may have been chopped off */
  gchar *file = report->filename
	     ? g_markup_printf_escaped (_("<b>Source:</b> %s"), report->filename)
	     : NULL;
  gchar *line = report->lineno
	     ? g_markup_printf_escaped (_("<b>Line:</b> %d"), report->lineno)
	     : NULL;
  gchar *stmt = report->linebuf
	     ? g_markup_printf_escaped (_("<b>Line:</b> %s"), report->linebuf)
	     : NULL;

  gchar *text = g_strconcat (message, file ? "\n" : "", file ? : "",
			    line ? "\n" : "", line ? : "",
			    stmt ? "\n" : "", stmt ? : "", NULL);

  g_free (message);
  g_free (line);
  g_free (file);
  g_free (stmt);
  se_store_text (type[report->flags & 3], text);
  g_free (text);
  se_js_icon (flag[report->flags & 3]);
}

int se_eval_ext (se_t *se, const gchar *script, se_o_t *obj,
		 se_print_cb_t print_cb, void *print_cb_data,
		 se_error_cb_t error_cb, const char *src)
{
  pthread_mutex_lock (&se->lock);

  se_print_cb_t old_print_cb      = se->print_cb;
  void *        old_print_cb_data = se->print_cb_data;
  se_error_cb_t old_error_cb      = se->error_cb;
  se->print_cb      = print_cb;
  se->print_cb_data = print_cb_data;
  se->error_cb      = error_cb;

  int r = JS_EvaluateScript (se->cx, obj ? obj->obj : se->global, script,
			    strlen (script), src ? : "gxine", 0, &se->rval);

  se->print_cb      = old_print_cb;
  se->print_cb_data = old_print_cb_data;
  se->error_cb      = old_error_cb;

  pthread_mutex_unlock (&se->lock);
  return r;
}

int se_eval (se_t *se, const gchar *script, se_o_t *obj,
	     se_print_cb_t print_cb, void *print_cb_data, const char *src)
{
  return se_eval_ext (se, script, obj, print_cb, print_cb_data, se_error_cb,
		      src);
}

gchar *se_result_str (se_t *se)
{
  if (!JSVAL_IS_STRING (se->rval))
    return NULL;

  se->str = JS_ValueToString (se->cx, se->rval);
  char *encoded = SE_JS_ENCODE_STRING(se->cx, se->str);
  char *res = strdup(encoded);
  SE_JS_FREE_ENCODED_STRING(se->cx, encoded);
  return res;
}

int se_result_int (se_t *se, JSInt32 *num)
{
  if (JSVAL_IS_INT (se->rval))
    return JS_ValueToInt32 (se->cx, se->rval, num);
  return 0;
}

int se_result_double (se_t *se, JSFloat64 *num)
{
  if (JSVAL_IS_DOUBLE (se->rval))
    return JS_ValueToNumber (se->cx, se->rval, num);
  return 0;
}

int se_result_bool (se_t *se, JSBool *num)
{
  if (JSVAL_IS_BOOLEAN (se->rval))
    return JS_ValueToBoolean (se->cx, se->rval, num);
  return 0;
}

int se_result_num_as_int (se_t *se, JSInt32 *num)
{
  if (JSVAL_IS_DOUBLE (se->rval))
  {
    JSFloat64 f;
    JS_ValueToNumber (se->cx, se->rval, &f);
    *num = floor (f);
    return 1;
  }
  return JS_ValueToInt32 (se->cx, se->rval, num);
}

int se_result_num_as_double (se_t *se, JSFloat64 *num)
{
  if (JSVAL_IS_INT (se->rval))
  {
    JSInt32 i;
    JS_ValueToInt32 (se->cx, se->rval, &i);
    *num = i;
    return 1;
  }
  return JS_ValueToNumber (se->cx, se->rval, num);
}

static inline jsval
se_js_string_val (JSContext *cx, const char *str)
{
  JSString *jstr = JS_NewStringCopyZ (cx, str);
  return STRING_TO_JSVAL (jstr);
}

/*
 * methods
 */

static JSBool controls_exit (JSContext *cx, uintN argc, jsval *vp)
{
  gchar *fname;

  se_log_fncall_checkinit ("controls.exit");

  player_stop ();

  if (gtk_main_level() < 1)
  {
    settings_clear ();
    exit (2);
  }

  mm_save ();
  playlist_save (NULL);
  save_key_bindings ();
  save_startup_script ();

  logprintf ("script_engine: saving config...\n");

  fname = get_config_filename (FILE_CONFIG);
  xine_config_save (xine, fname);
  g_free (fname);

  fname = get_config_filename (FILE_ACCELS);
  gtk_accel_map_save (fname);
  g_free (fname);

  { /* lie about remembering the volume... */
    xine_cfg_entry_t entry;
    if (xine_config_lookup_entry (xine, "audio.volume.remember_volume", &entry))
    {
      entry.num_value = 0;
      xine_config_update_entry (xine, &entry);
    }
  }
  settings_clear ();

  gtk_main_quit();
  sched_yield ();

  exit (0);
}

static JSBool show_js_console (JSContext *cx, uintN argc, jsval *vp)
{
  se_log_fncall_checkinit ("show_js_console");
  if (gtk_widget_get_visible (se_window))
    gtk_widget_hide (se_window);
  else
  {
    window_show (se_window, NULL);
    se_prop_set_int (gse, js_icon_obj, "v", 0);
  }

  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static JSBool
js_callback (JSContext *cx, uintN argc, jsval *vp)
{
  jsval *argv = JS_ARGV (cx, vp);
  se_t *se = (se_t *) JS_GetContextPrivate (cx);
  se_log_fncall ("callback");
  se_argc_check_range (1, 2, "callback");
  se_arg_is_string (0, "callback");
  if (argc > 1)
    se_arg_is_object (1, "callback");
  char *str = SE_JS_ENCODE_STRING (cx, JS_ValueToString (cx, argv[0]));
  engine_queue_push (str,
 		     JS_GetPrivate (cx, argc > 1 ? JSVAL_TO_OBJECT (argv[1])
 						 : se->global),
 		     NULL, NULL, NULL, _("JS callback"));
  SE_JS_FREE_ENCODED_STRING (cx, str);
  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static JSBool
js_xine_cfg_get (JSContext *cx, uintN argc, jsval *vp)
{
  jsval *argv = JS_ARGV (cx, vp);
  se_log_fncall ("xine_cfg_get");
  se_argc_check (1, "xine_cfg_get");
  se_arg_is_string (0, "xine_cfg_get");

  xine_cfg_entry_t entry;
  char *cfg = SE_JS_ENCODE_STRING (cx, JS_ValueToString (cx, argv[0]));
  if (!xine_config_lookup_entry (xine, cfg, &entry))
  {
    JS_SET_RVAL (cx, vp, JSVAL_NULL);
    SE_JS_FREE_ENCODED_STRING (cx, cfg);
    return JS_TRUE;
  }

  SE_JS_FREE_ENCODED_STRING(cx, cfg);

  switch (entry.type)
  {
  case XINE_CONFIG_TYPE_RANGE:
  case XINE_CONFIG_TYPE_NUM:
    JS_SET_RVAL (cx, vp, INT_TO_JSVAL (entry.num_value));
    return JS_TRUE;

  case XINE_CONFIG_TYPE_BOOL:
    JS_SET_RVAL (cx, vp, BOOLEAN_TO_JSVAL (entry.num_value));
    return JS_TRUE;

  case XINE_CONFIG_TYPE_STRING:
    JS_SET_RVAL (cx, vp, se_js_string_val (cx, entry.str_value ? : entry.str_default));
    return JS_TRUE;

  case XINE_CONFIG_TYPE_ENUM:
    JS_SET_RVAL (cx, vp, se_js_string_val (cx, entry.enum_values[entry.num_value]));
    return JS_TRUE;

  default:
    JS_SET_RVAL (cx, vp, JSVAL_NULL);
    return JS_TRUE;
  }
}

static JSBool
js_xine_cfg_set (JSContext *cx, uintN argc, jsval *vp)
{
  jsval *argv = JS_ARGV (cx, vp);
  se_log_fncall ("xine_cfg_set");
  se_argc_check (2, "xine_cfg_set");
  se_arg_is_string (0, "xine_cfg_set");

  xine_cfg_entry_t entry;
  char *cfg = SE_JS_ENCODE_STRING (cx, JS_ValueToString(cx, argv[0]));
  if (!xine_config_lookup_entry (xine, cfg, &entry))
  {
    JS_SET_RVAL (cx, vp, JSVAL_VOID);
    SE_JS_FREE_ENCODED_STRING (cx, cfg);
    return JS_TRUE;
  }

  SE_JS_FREE_ENCODED_STRING (cx, cfg);

  int32 num;
  switch (entry.type)
  {
  case XINE_CONFIG_TYPE_RANGE:
    se_arg_is_int_or_bool (1, "xine_cfg_set");
    JS_ValueToInt32 (cx, argv[1], &num);
    entry.num_value = MIN (entry.range_max, MAX (entry.range_min, num));
    break;

  case XINE_CONFIG_TYPE_NUM:
    se_arg_is_int_or_bool (1, "xine_cfg_set");
    JS_ValueToInt32 (cx, argv[1], &num);
    entry.num_value = num;
    break;

  case XINE_CONFIG_TYPE_BOOL:
    se_arg_is_int_or_bool (1, "xine_cfg_set");
    JS_ValueToInt32 (cx, argv[1], &num);
    entry.num_value = !!num;
    break;

  case XINE_CONFIG_TYPE_STRING:
    se_arg_is_string (1, "xine_cfg_set");
    entry.str_value = SE_JS_ENCODE_STRING (cx, JS_ValueToString (cx, argv[1]));
    break;

  case XINE_CONFIG_TYPE_ENUM:
    se_arg_is_string (1, "xine_cfg_set");
    char *v = SE_JS_ENCODE_STRING (cx, JS_ValueToString (cx, argv[1]));
    int i;
    for (i = 0; entry.enum_values[i]; ++i)
      if (!strcmp (v, entry.enum_values[i])) {
        SE_JS_FREE_ENCODED_STRING (cx, v);
	break;
      }
    if (entry.enum_values[i])
      entry.num_value = i;
    SE_JS_FREE_ENCODED_STRING (cx, v);
    break;

  default:
    JS_SET_RVAL (cx, vp, JSVAL_VOID);
    return JS_TRUE;
  }

  preferences_update_entry (&entry);

  if (entry.type == XINE_CONFIG_TYPE_STRING)
    SE_JS_FREE_ENCODED_STRING (cx, entry.str_value);

  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static char *show_help_int (se_t *se, se_o_t *obj, se_group_t selector)
{
  const GList *objs;
  char *help = NULL;

  foreach_glist (objs, obj->functions)
  {
    se_f_t *cmd = objs->data;
    if (cmd->group == selector)
      asreprintf (&help, "%s    %s (%s)%s%s%s\n",
		  help ? : "", se_id (se, obj, cmd->id),
		  cmd->arghelp ? : "", cmd->funchelp ? "  /* " : "",
		  cmd->funchelp ? : "", cmd->funchelp ? " */" : "");
  }

  foreach_glist (objs, obj->children)
  {
    char *group = show_help_int (se, objs->data, selector);
    if (group)
    {
      asreprintf (&help, "%s%s", help ? : "", group);
      free (group);
    }
  }

  foreach_glist (objs, obj->children)
  {
    se_o_t *o = objs->data;
    if (o->group == selector)
      asreprintf (&help, "%s    %s\t%s%s%s\n",
		  help ? : "", se_id (se, obj, o->id),
		  o->help ? "  /* " : "", o->help ? : "",
		  o->help ? " */" : "");
  }

  return help;
}

static JSBool show_help (JSContext *cx, uintN argc, jsval *vp)
{
  static const char *const group_id[] = {
    NULL,
    N_("Engine control:"),
    N_("Dialogue boxes:"),
    N_("Playlist control:"),
    N_("File access:"),
    N_("Status:"),
    N_("Properties:"),
    N_("Input event generation (DVD menus etc.):"),
    N_("External program support:"),
    NULL
  };

  int i;
  se_t *se = (se_t *) JS_GetContextPrivate(cx);

  se_log_fncall_checkinit ("help");

  se->print_cb (se->print_cb_data, "\n%s",
		_("Available commands (gxine Javascript interface):"));

  i = 0;
  while (group_id[++i])
  {
    char *help = NULL;
    const GSList *pse;

    foreach_glist (pse, se_chain)
    {
      char *group = show_help_int (se, ((se_t *)pse->data)->g, i);
      if (group)
      {
	asreprintf (&help, "%s%s  %s\n%s", help ? : "", help ? "\n" : "",
		    gettext (group_id[i]), group);
	free (group);
      }
    }

    if (!help)
      continue;
    help[strlen (help) - 1] = 0;
    se->print_cb (se->print_cb_data, "%s", help);
    free (help);
  }

  /* set_audio_channel (int);
   * set_spu_channel (int);
   * int get_pos ();
   * int get_time ();
   * int get_length ();
   */
  JS_SET_RVAL (cx, vp, JSVAL_VOID);
  return JS_TRUE;
}

static int get_prop_int (se_t *se, se_o_t *o, se_prop_t *p)
{
  int n;

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.i;
  }

  if (sscanf (p->value, "%d", &n) == 1)
  {
    logprintf ("script_engine: this is a simple integer %d\n", n);
    return n;
  }
  if (!strncasecmp (p->value, "jscript:", 8))
  {
    char *script = p->value + 8;
    logprintf ("script_engine: this is a jscript => evaluating '%s'...\n",
	       script);
    se_eval (se, script, o, NULL, NULL, "JS property");
    if (se_result_num_as_int (se, &n))
    {
      logprintf ("script_engine: result: %d\n", n);
      return n;
    }
    g_printerr (_("script_engine: error: non-numeric result in %s==‘%s’\n"),
	     p->id, p->value);
  } else
    g_printerr (_("script_engine: error: non-numeric property %s==‘%s’\n"),
	     p->id, p->value);

  return 0;
}

static double get_prop_double (se_t *se, se_o_t *o, se_prop_t *p)
{
  double n;

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.d;
  }

  if (sscanf (p->value, "%lf", &n) == 1)
  {
    logprintf ("script_engine: this is a simple float %lf\n", n);
    return n;
  }
  if (!strncasecmp (p->value, "jscript:", 8))
  {
    char *script = p->value + 8;
    logprintf ("script_engine: this is a jscript => evaluating '%s'...\n",
	       script);
    se_eval (se, script, o, NULL, NULL, "JS property");
    if (se_result_num_as_double (se, &n))
    {
      logprintf ("script_engine: result: %lf\n", n);
      return n;
    }
    g_printerr (_("script_engine: error: non-numeric result in %s==‘%s’\n"),
	     p->id, p->value);
  } else
    g_printerr (_("script_engine: error: non-numeric property %s==‘%s’\n"),
	     p->id, p->value);

  return 0;
}

static JSBool get_prop_bool (se_t *se, se_o_t *o, se_prop_t *p)
{
  int n;

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.i ? JS_TRUE : JS_FALSE;
  }

  if (sscanf (p->value, "%d", &n) == 1)
  {
    n = !!n;
    logprintf ("script_engine: this is a simple boolean %d\n", n);
    return n ? JS_TRUE : JS_FALSE;
  }
  return strcasecmp (p->value, "false") ? JS_TRUE : JS_FALSE;
}

static const char *get_prop_string (se_t *se, se_o_t *o, se_prop_t *p)
{
  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.s;
  }

  return p->value;
}

static jsval get_prop_jsval (se_t *se, se_o_t *o, se_prop_t *p)
{
  logprintf ("script_engine: getting value of %s=='%s'\n", p->id, p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    {
      int n = get_prop_int (se, o, p);
      return INT_TO_JSVAL (n);
    }

  case SE_TYPE_DOUBLE:
    {
      jsval rval;
      SE_JS_NEW_NUMBER_VAL (se->cx, get_prop_double (se, o, p), rval);
      return rval;
    }

  case SE_TYPE_BOOL:
    {
      JSBool b = get_prop_bool (se, o, p);
      return BOOLEAN_TO_JSVAL (b);
    }

  case SE_TYPE_STRING:
    {
      const char *s = get_prop_string (se, o, p);
      return se_js_string_val (se->cx, s);
    }

  default:
    g_printerr (_("script_engine: error: property %s has unknown type %d\n"),
	     p->id, p->se_type);
  }

  return BOOLEAN_TO_JSVAL (0);
}

static void set_prop_jsval (se_t *se, se_o_t *o, se_prop_t *p, const jsval *v)
{
  logprintf ("script_engine: setting value of %s\n", p->id);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    if (JSVAL_IS_NUMBER(*v))
      se_prop_set_int (se, o, p->id, JSVAL_TO_INT(*v));
    else
      se->print_cb (se->print_cb_data, _("\n%s.%s: value must be numeric\n"),
		    o->id, p->id);
    return;

  case SE_TYPE_DOUBLE:
    if (JSVAL_IS_NUMBER(*v))
      se_prop_set_double (se, o, p->id, SE_JSVAL_TO_DOUBLE(*v));
    else
      se->print_cb (se->print_cb_data, _("\n%s.%s: value must be numeric\n"),
		    o->id, p->id);
    return;

  case SE_TYPE_BOOL:
    if (JSVAL_IS_BOOLEAN(*v))
      se_prop_set_bool (se, o, p->id, JSVAL_TO_BOOLEAN(*v));
    else
      se->print_cb (se->print_cb_data, _("\n%s.%s: value must be boolean\n"),
		    o->id, p->id);
    return;

  case SE_TYPE_STRING:
    {
      JSString *str = JSVAL_TO_STRING(*v);
      char     *string = SE_JS_ENCODE_STRING (se->cx, str);
      se_prop_set (se, o, p->id, string);
      SE_JS_FREE_ENCODED_STRING (se->cx, str);
    }
    return;

  default:
    g_printerr (_("script_engine: error: property %s has unknown type %d\n"),
	     p->id, p->se_type);
  }
}


/*
 * function to create and maintain js objects
 */

SE_JS_PROPERTY_GETTER_DECLARE (generic_JSGetProperty)
{
  SE_JS_PROPERTY_GETTER_INIT_VARIABLES (id_val);
  se_t   *se = (se_t *) JS_GetContextPrivate(cx);
  se_log_fncall ("generic get property");
  se_o_t *o = JS_GetPrivate (cx, obj);

  if (JSVAL_IS_STRING (id_val))
  {
    JSString *str = JS_ValueToString (cx, id_val);
    char     *prop = str ? SE_JS_ENCODE_STRING (cx, str) : "";
    GList    *n;

    logprintf ("script_engine: looking for generic property '%s' in '%s'\n",
	       prop, o->id);

    /* look through properties */

    foreach_glist (n, o->properties)
    {
      se_prop_t *p = (se_prop_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	*vp = get_prop_jsval (se, o, p);
        if (str)
          SE_JS_FREE_ENCODED_STRING (cx, prop);

	return JS_TRUE;
      }
    }

    /* look through objects for default value */

    foreach_glist (n, o->children)
    {
      se_o_t *p = (se_o_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	jsval prop_val = se_js_string_val (cx, ".");
        if (!SE_JS_CALL_PROPERTY_GETTER_WITH_JSVAL (generic_JSGetProperty, cx, p->obj, prop_val, vp))
	  *vp = OBJECT_TO_JSVAL (p->obj);
        if (str)
          SE_JS_FREE_ENCODED_STRING (cx, prop);

	return JS_TRUE;
      }
    }

    if (*prop && o->parent) {
      JSBool ret = generic_JSGetProperty (cx, o->parent->obj, id, vp);
      if (str)
        SE_JS_FREE_ENCODED_STRING (cx, prop);
      return ret;
    }

    if (str)
      SE_JS_FREE_ENCODED_STRING (cx, prop);
    return JS_TRUE;
  }

  *vp = JSVAL_VOID;

  return JS_FALSE;
}

SE_JS_PROPERTY_SETTER_DECLARE(generic_JSSetProperty)
{
  SE_JS_PROPERTY_SETTER_INIT_VARIABLES (id_val);
  se_t   *se = (se_t *) JS_GetContextPrivate(cx);
  se_o_t *o;

  se_log_fncall ("generic set property");

  o = JS_GetPrivate (cx, obj);

  if (JSVAL_IS_STRING (id_val))
  {
    JSString *str = JS_ValueToString (cx, id_val);
    char     *prop = SE_JS_ENCODE_STRING (cx, str);
    GList    *n;

    logprintf ("script_engine: looking for generic property '%s' in '%s'\n",
	       prop, o->id);

    /* look through properties */

    foreach_glist (n, o->properties)
    {
      se_prop_t *p = (se_prop_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	if (!p->constant)
	  set_prop_jsval (se, o, p, vp);
        SE_JS_FREE_ENCODED_STRING (cx, prop);
	return JS_TRUE;
      }
    }

    /* look through objects for default value */

    foreach_glist (n, o->children)
    {
      se_o_t *p = (se_o_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	static jsval dot = {0};
	static int dot_init = 0;
	if (!dot_init)
	{
	  dot_init = 1;
	  dot = se_js_string_val (cx, ".");
	}
        if (!SE_JS_CALL_PROPERTY_SETTER_WITH_JSVAL (generic_JSSetProperty, cx, p->obj, dot, vp))
	  *vp = OBJECT_TO_JSVAL (p->obj);
	return JS_TRUE;
      }
    }

    if (*prop && o->parent)
    {
      JSBool ret = SE_JS_CALL_PROPERTY_SETTER_WITH_JSVAL (generic_JSSetProperty, cx, o->parent->obj, id_val, vp);
      SE_JS_FREE_ENCODED_STRING (cx, prop);
      return ret;
    }

    SE_JS_FREE_ENCODED_STRING (cx, prop);
    return JS_TRUE;
  }

  *vp = JSVAL_VOID;

  return JS_FALSE;
}

static void generic_JSDestructor (JSContext *cx, JSObject *obj)
{
  se_log_fncall ("generic destructor");
}

#ifdef JSFUN_CONSTRUCTOR
static JSBool generic_Constructor (JSContext* cx, uintN argc, jsval* vp)
{
  JSObject* obj = JS_NewObjectForConstructor (cx, vp);
  if (!obj) {
    JS_ReportError (cx, "Failed to create 'this' object");
    return JS_FALSE;
  }

  JS_SET_RVAL (cx, vp, OBJECT_TO_JSVAL (obj));
  return JS_TRUE;
}
#endif

se_o_t *se_create_object (se_t *se, se_o_t *parent /* may be NULL */,
			  const gchar *name, void *user_data,
			  se_group_t group, const gchar *help)
{
  static JSClass generic_JSClass = {
    .name        = "view",
    .flags       = JSCLASS_HAS_PRIVATE,
    .addProperty = JS_PropertyStub,
    .delProperty = JS_PropertyStub,
    .getProperty = generic_JSGetProperty,
    .setProperty = generic_JSSetProperty,
    .enumerate   = JS_EnumerateStub,
    .resolve     = JS_ResolveStub,
    .convert     = JS_ConvertStub,
    .finalize    = generic_JSDestructor,
#ifdef JSFUN_CONSTRUCTOR
    .construct   = generic_Constructor,
#endif
  };

  se_o_t *o;

  logprintf ("script_engine: creating object %s parent: %s\n",
	     name, se_id (se, parent, NULL));

  o = (se_o_t *) malloc (sizeof (se_o_t));

  if (!parent)
    o->obj = JS_DefineObject (se->cx, se->global, name,
			      &generic_JSClass, NULL, 0);
  else
    o->obj = JS_DefineObject (se->cx, parent->obj, name,
			      &generic_JSClass, NULL, 0);

  o->user_data   = user_data;
  o->parent      = parent;
  o->id          = strdup (name);
  o->children    = NULL;
  o->properties  = NULL;
  o->functions   = NULL;
  o->group	 = group;
  o->help	 = help;

  JS_SetPrivate (se->cx, o->obj, o);

  if (parent)
    parent->children = g_list_append (parent->children, o);
  else
    se->g->children = g_list_append (se->g->children, o);

  return o;
}

se_o_t *se_find_object (se_t *se, se_o_t *parent, const char *name)
{
  GList *objs;
  while (strchr (name, '.'))
  {
    int index = strchr (name, '.') - name;
    foreach_glist (objs, parent ? parent->children : se->g->children)
    {
      se_o_t *o = objs->data;
      if (!strncasecmp (o->id, name, index) && !o->id[index])
      {
	name += index + 1;
	parent = o;
	break;
      }
    }
    if (!objs)
      return NULL;
  }
  foreach_glist (objs, parent ? parent->children : se->g->children)
  {
    se_o_t *o = objs->data;
    if (!strcasecmp (o->id, name))
      return o;
  }
  return NULL;
}

static se_o_t dflt;

se_o_t *se_get_object (se_t *se, se_o_t *parent, const char *name)
{
  se_o_t *o = se_find_object (se, parent, name);
  if (o)
    return o;
  free (dflt.id);
  dflt.id = strdup (name);
  return &dflt;
}

static se_prop_t *se_prop_find (se_t *se, se_o_t *o, const char *id)
{
  GList *node;
  foreach_glist (node, o ? o->properties : se->g->properties)
  {
    se_prop_t *p = node->data;
    if (!strcasecmp (p->id, id))
      return p;
  }
  return NULL;
}

static void se_prop_listeners (se_t *se, se_o_t *o, se_prop_t *p, se_prop_read_t r)
{
  GList *node;
  foreach_glist (node, p->listeners)
  {
    const se_prop_listener_t *l = node->data;
    l->cb (l->user_data, se, o, p, r);
  }
}

void se_prop_set (se_t *se, se_o_t *o, const char *id, const char *value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_STRING)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	       "", se_id (se, o, id));
      return;
    }
    free (p->value);
    v.s = p->value = strdup (value);
    p->se_type = SE_TYPE_STRING;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create (se, o, id, value, SE_TYPE_STRING, 0, FALSE);
}

void se_prop_set_int (se_t *se, se_o_t *o, const char *id, int value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_INT)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	       "_int", se_id (se, o, id));
      return;
    }
    free (p->value);
    p->value = g_strdup_printf ("%d", v.i = value);
    p->se_type = SE_TYPE_INT;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create_int (se, o, id, value, FALSE);
}

void se_prop_set_double (se_t *se, se_o_t *o, const char *id, double value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_DOUBLE)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	      "_double", se_id (se, o, id));
      return;
    }
    free (p->value);
    p->value = g_strdup_printf ("%la", v.d = value);
    p->se_type = SE_TYPE_DOUBLE;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create_double (se, o, id, value, FALSE);
}

void se_prop_set_bool (se_t *se, se_o_t *o, const char *id, int value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_BOOL)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	       "_bool", se_id (se, o, id));
      return;
    }
    free (p->value);
    p->value = g_strdup_printf ("%d", v.i = !!value);
    p->se_type = SE_TYPE_BOOL;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create_bool (se, o, id, value, FALSE);
}

char *se_prop_get (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "", se_id (se, o, id));
    return NULL;
  }

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    switch (p->se_type)
    {
    case SE_TYPE_INT:
      free (p->value);
      return p->value = g_strdup_printf ("%d", v.i);
    case SE_TYPE_BOOL:
      free (p->value);
      return p->value = strdup (v.i ? "true" : "false");
    case SE_TYPE_DOUBLE:
      free (p->value);
      return p->value = g_strdup_printf ("%la", v.d);
    case SE_TYPE_STRING:
      free (p->value);
      return p->value = strdup (v.s);
    default:;
    }
  }

  return p->value;
}

int se_prop_get_int (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "_int", se_id (se, o, id));
    return 0;
  }

  logprintf ("script_engine: getting value of %s=='%s'\n",
	     se_id (se, o, id), p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    return get_prop_int (se, o, p);

  case SE_TYPE_DOUBLE:
    return floor (get_prop_double (se, o, p));

  case SE_TYPE_BOOL:
    return get_prop_bool (se, o, p);

  default:
    g_printerr (_("script_engine: se_prop_get%s error: %s is not numeric\n"),
	     "_int", se_id (se, o, id));
  }
  return 0;
}

double se_prop_get_double (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "_double", se_id (se, o, id));
    return 0;
  }

  logprintf ("script_engine: getting value of %s=='%s'\n",
	     se_id (se, o, id), p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    return 0.0 + get_prop_int (se, o, p);

  case SE_TYPE_DOUBLE:
    return get_prop_double (se, o, p);

  case SE_TYPE_BOOL:
    return 0.0 + get_prop_bool (se, o, p);

  default:
    g_printerr (_("script_engine: se_prop_get%s error: %s is not numeric\n"),
	     "_double", se_id (se, o, id));
  }
  return 0;
}

JSBool se_prop_get_bool (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "_bool", se_id (se, o, id));
    return JS_FALSE;
  }

  logprintf ("script_engine: getting value of %s=='%s'\n",
	     se_id (se, o, id), p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    return get_prop_int (se, o, p) ? JS_TRUE : JS_FALSE;

  case SE_TYPE_DOUBLE:
    return (get_prop_double (se, o, p) != 0) ? JS_TRUE : JS_FALSE;

  case SE_TYPE_BOOL:
    return get_prop_bool (se, o, p);

  default:
    g_printerr (_("script_engine: se_prop_get%s error: %s is not numeric\n"),
	     "_bool", se_id (se, o, id));
  }
  return JS_FALSE;
}

void se_prop_create (se_t *se, se_o_t *o,
		     const char *id, const char *value,
		     se_prop_type_t se_type, intptr_t data, gboolean constant)
{
  if (!o)
    o = se->g;

  logprintf ("script_engine: setting %s to '%s'\n", se_id (se, o, id), value);

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    logprintf ("script_engine: creating new property %s\n", se_id (se, o, id));
    p = malloc (sizeof (se_prop_t));
    p->id = strdup (id);
    p->value = strdup (value);
    p->reader = NULL;
    p->listeners = NULL;
    p->data = data;
    o->properties = g_list_append (o->properties, p);
  }

  p->se_type = se_type;
  p->constant = constant;
}

void se_prop_create_with_reader (se_t *se, se_o_t *o,
				 const char *id, const char *value,
				 se_prop_type_t se_type, intptr_t data,
				 gboolean constant, se_prop_reader_t reader)
{ se_prop_create (se, o, id, value, se_type, data, constant);
  se_prop_set_reader_func (se, o, id, reader);
}

void se_prop_create_int (se_t *se, se_o_t *o, const char *id, int value,
			 gboolean constant)
{
  char str[24];
  snprintf (str, sizeof (str), "%d", value);
  se_prop_create (se, o, id, str, SE_TYPE_INT, 0, constant);
}

void se_prop_create_double (se_t *se, se_o_t *o, const char *id, double value,
			    gboolean constant)
{
  char str[24];
  snprintf (str, sizeof (str), "%la", value); /* C99 hex float */
  se_prop_create (se, o, id, str, SE_TYPE_DOUBLE, 0, constant);
}

void se_prop_create_bool (se_t *se, se_o_t *o, const char *id, int value,
			  gboolean constant)
{
  char str[24];
  snprintf (str, sizeof (str), "%d", !!value);
  se_prop_create (se, o, id, str, SE_TYPE_BOOL, 0, constant);
}

static void se_prop_read_xine_id_int (const se_prop_t *prop, se_prop_read_t *value)
{
  xine_cfg_entry_t entry;
  if (xine_config_lookup_entry (xine, (const char *) prop->data, &entry))
    value->i = entry.num_value;
  else
    value->i = 0;
}

static void se_prop_read_xine_id_string (const se_prop_t *prop, se_prop_read_t *value)
{
  xine_cfg_entry_t entry;
  if (xine_config_lookup_entry (xine, (const char *) prop->data, &entry))
    value->s = entry.str_value;
  else
    value->s = "";
}

void se_prop_create_xine_id (se_t *se, se_o_t *o,
			     const char *id, const char *xine_id)
{
  xine_cfg_entry_t entry = { NULL };

  if (!xine_config_lookup_entry (xine, xine_id, &entry)) {
    entry.type = XINE_CONFIG_TYPE_UNKNOWN;
  }

  switch (entry.type)
  {
  case XINE_CONFIG_TYPE_RANGE:
  case XINE_CONFIG_TYPE_NUM:
    se_prop_create_with_reader (se, o, id, ".", SE_TYPE_INT,
				(intptr_t) xine_id, FALSE,
				se_prop_read_xine_id_int);
    break;
  case XINE_CONFIG_TYPE_ENUM:
    g_printerr (_("script_engine: can't create property for xine config enum type\n"));
    break;
  case XINE_CONFIG_TYPE_STRING:
    se_prop_create_with_reader (se, o, id, ".", SE_TYPE_STRING,
				(intptr_t) xine_id, FALSE,
				se_prop_read_xine_id_string);
    break;
  case XINE_CONFIG_TYPE_BOOL:
    se_prop_create_with_reader (se, o, id, ".", SE_TYPE_BOOL,
				(intptr_t) xine_id, FALSE,
				se_prop_read_xine_id_int);
    break;
  default:
    g_printerr (_("script_engine: can't create property for xine config unknown type\n"));
  }
}

static void se_prop_read_xine_param (const se_prop_t *prop, se_prop_read_t *value)
{
  value->i = xine_get_param (stream, prop->data);
}

void se_prop_create_xine_param (se_t *se, se_o_t *o,
				const char *id, int xine_param,
				se_prop_type_t type)
{
  switch (type)
  {
  case SE_TYPE_BOOL:
  case SE_TYPE_INT:
    se_prop_create_with_reader (se, o, id, "", type, xine_param, FALSE,
				se_prop_read_xine_param);
    return;
  default:
    g_printerr (_("script_engine: can't create property for xine param non-int type\n"));
  }
}

void se_prop_add_listener (se_t *se, se_o_t *o, const char *id,
			   se_prop_cb_t prop_cb, void *user_data)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  logprintf ("script_engine: adding listener to %s\n", se_id (se, o, id));

  p = (o == &dflt) ? NULL : se_prop_find (se, o, id);
  if (!p)
    g_printerr (_("script_engine: error in %s: property %s not found\n"),
	     __func__, se_id (se, o, id));
  else
  {
    se_prop_listener_t *listener = malloc (sizeof (se_prop_listener_t));
    listener->cb = prop_cb;
    listener->user_data = user_data;
    p->listeners = g_list_append (p->listeners, listener);
  }
}

void se_prop_set_reader_func (se_t *se, se_o_t *o, const char *id,
			      se_prop_reader_t reader)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  logprintf ("script_engine: setting reader for %s\n", se_id (se, o, id));

  p = se_prop_find (se, o, id);
  if (!p)
    g_printerr (_("script_engine: error in %s: property %s not found\n"),
	     __func__, se_id (se, o, id));
  else
    p->reader = reader;
}

static void error_reporter (JSContext *cx, const char *message,
			    JSErrorReport *report)
{
  se_t *se = (se_t *) JS_GetContextPrivate(cx);
  if (se->error_cb)
    se->error_cb (message, report);
  if (se->print_cb && se->print_cb != se_result_cb)
    se->print_cb (se->print_cb_data, "%s", message);
  logprintf ("script_engine: JSError '%s'\n", message);
}

se_f_t *se_defun (se_t *se, se_o_t *o /* may be NULL */,
		  const char *name, JSNative fun, uintN nargs, uintN attrs,
		  se_group_t group, const char *arghelp, const char *funchelp)
{
  se_f_t *f;
  f = malloc (sizeof(se_f_t));
  f->id = strdup (name);

  if (o)
  {
    f->fun = JS_DefineFunction (se->cx, o->obj, name, fun, nargs, attrs);
    o->functions = g_list_append (o->functions, f);
  }
  else
  {
    f->fun = JS_DefineFunction (se->cx, se->global, name, fun, nargs, attrs);
    se->g->functions = g_list_append (se->g->functions, f);
  }

  f->group = group;
  f->arghelp = arghelp ? gettext (arghelp) : NULL;
  f->funchelp = funchelp ? gettext (funchelp) : NULL;

  return f;
}

void se_defuns (se_t *se, se_o_t *o /* may be NULL */, const se_f_def_t defs[])
{
  int i = -1;
  while (defs[++i].name)
    se_defun (se, o, defs[i].name, defs[i].func, defs[i].nargs, defs[i].attrs,
	      defs[i].group, defs[i].arghelp, defs[i].funchelp);
}

static void se_execute_cb (GtkWidget *widget, char *js)
{
  engine_exec (js, se_result_cb, NULL, _("Javascript console"));
}

static void se_response_cb (GtkWidget *widget, int response, gpointer data)
{
  switch (response)
  {
  case GTK_RESPONSE_REJECT:
    gtk_list_store_clear (GTK_LIST_STORE (se_error_store));
    break;
  default:
    gtk_widget_hide (se_window);
  }
}

static gboolean
console_scroll_cb (GtkWidget *w, GdkEventKey *e, GtkWidget *t)
{
  GtkAdjustment *adj = gtk_tree_view_get_vadjustment (tree_view);
  gdouble value = gtk_adjustment_get_value (adj);

  switch (e->state & GXINE_MODIFIER_MASK)
  {
  default:
    return FALSE;

  case 0:
    switch (e->keyval)
    {
    default:		return FALSE;
    case GDK_Page_Up:   value -= gtk_adjustment_get_page_increment (adj); break;
    case GDK_Page_Down: value += gtk_adjustment_get_page_increment (adj); break;
    }
    break;

  case GDK_SHIFT_MASK:
    switch (e->keyval)
    {
    default:		return FALSE;
    case GDK_Up:        value -= gtk_adjustment_get_step_increment (adj); break;
    case GDK_Down:      value += gtk_adjustment_get_step_increment (adj); break;
    case GDK_Page_Up:   value -= gtk_adjustment_get_page_increment (adj); break;
    case GDK_Page_Down: value += gtk_adjustment_get_page_increment (adj); break;
    }
    break;

  case GDK_CONTROL_MASK:
    switch (e->keyval)
    {
    default:		return FALSE;
    case GDK_Page_Up:   value = gtk_adjustment_get_lower (adj); break;
    case GDK_Page_Down: value = gtk_adjustment_get_upper (adj); break;
    }
    break;
  }

  if (value < gtk_adjustment_get_lower(adj))
    value = gtk_adjustment_get_lower (adj);
  else if (value > gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj))
    value = gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj);

  gtk_adjustment_set_value (adj, value);
  return TRUE;
}

static void
se_create_console (void)
{
  GtkWidget *w, *b;
  GtkCellRenderer *cell;
  GtkTreeViewColumn *column;

  se_window = gtk_dialog_new_with_buttons (_("Javascript console"), NULL, 0,
				GTK_STOCK_CLEAR, GTK_RESPONSE_REJECT,
				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
				NULL);
  gtk_window_set_default_size (GTK_WINDOW (se_window), 480, 250);
  g_object_connect (se_window,
	"signal::delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL,
	"signal::response", G_CALLBACK (se_response_cb), NULL,
	NULL);

  w = gtk_hbox_new (FALSE, 2);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (se_window))),
                      w, FALSE, FALSE, 2);

  eval_box = history_combo_box_new (se_execute_cb);
  gtk_box_pack_start (GTK_BOX (w), eval_box, TRUE, TRUE, 0);

  b = gtk_button_new_from_stock (GTK_STOCK_EXECUTE);
  gtk_box_pack_start (GTK_BOX (w), b, FALSE, FALSE, 0);
  history_combo_box_set_activate_button (eval_box, b);

  b = ui_button_new_stock_mnemonic (GTK_STOCK_CLEAR, _("_Flush"));
  gtk_box_pack_start (GTK_BOX (w), b, FALSE, FALSE, 0);
  g_signal_connect_swapped (b, "clicked",
			    G_CALLBACK (history_combo_box_clear), eval_box);

  se_error_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
  tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model
			       (GTK_TREE_MODEL (se_error_store)));
  gtk_tree_view_set_rules_hint (tree_view, TRUE);
  gtk_tree_view_set_reorderable (tree_view, FALSE);
  gtk_tree_view_set_headers_visible (tree_view, FALSE);

  cell = gtk_cell_renderer_pixbuf_new ();
  g_object_set (cell, "stock-size", (guint) GTK_ICON_SIZE_DIALOG, NULL);
  column = gtk_tree_view_column_new_with_attributes
	     ("", cell, "stock-id", 0, NULL);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (tree_view, column);

  cell = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes
	     ("", cell, "markup", 1, NULL);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (tree_view, column);

  w = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (tree_view));

  g_signal_connect (G_OBJECT (eval_box), "key-press-event",
		    G_CALLBACK (console_scroll_cb), w);

  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (se_window))),
                      w, TRUE, TRUE, 0);
}

se_t *se_new (void)
{
  static JSClass global_class = {
    .name        = "global",
    .flags       = JSCLASS_HAS_PRIVATE | JSCLASS_GLOBAL_FLAGS,
    .addProperty = JS_PropertyStub,
    .delProperty = JS_PropertyStub,
    .getProperty = JS_PropertyStub,
    .setProperty = JS_StrictPropertyStub,
    .enumerate   = JS_EnumerateStub,
    .resolve     = JS_ResolveStub,
    .convert     = JS_ConvertStub,
    .finalize    = JS_FinalizeStub,
  };

  se_t    *se;

  /*
   * create the JS console window
   */

  if (!se_window)
    se_create_console ();

  /*
   * create a js engine instance
   */

  if (!rt)
    rt = JS_NewRuntime(0x100000);

  se = malloc (sizeof (se_t));

  se->cx       = JS_NewContext (rt, 0x1000);
#ifdef HAVE_COMPARTMENTS
  se->global   = JS_NewCompartmentAndGlobalObject (se->cx, &global_class, NULL);
  /* Select the compartment our global object is in */
  JS_EnterCrossCompartmentCall (se->cx, se->global);
#else
  se->global   = JS_NewObject (se->cx, &global_class, NULL, NULL);
#endif

  pthread_mutexattr_t attr;
  pthread_mutexattr_init (&attr);
#ifdef PTHREAD_MUTEX_RECURSIVE_NP
  pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE_NP);
#else /* *BSD does not have the _NP in the name */
  pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
#endif
  pthread_mutex_init (&se->lock, &attr);
  pthread_mutexattr_destroy (&attr);

  JS_InitStandardClasses (se->cx, se->global);
  JS_SetErrorReporter (se->cx, error_reporter);
  JS_SetContextPrivate (se->cx, se);

  /*
   * create fake global se_o
   */

  se->g = malloc (sizeof(se_o_t));
  se->g->obj        = se->global;
  se->g->parent     = NULL;
  se->g->id         = strdup("_global_");
  se->g->children   = NULL;
  se->g->functions  = NULL;
  se->g->properties = NULL;

  se_chain = g_slist_append (se_chain, se);

  {
    static const se_f_def_t defs[] = {
      { "exit", controls_exit, 0, JSFUN_FAST_NATIVE, SE_GROUP_ENGINE, NULL, NULL },
      { "js_console_show", show_js_console, 0, JSFUN_FAST_NATIVE, SE_GROUP_DIALOGUE, NULL, NULL },
      { "help", show_help, 0, JSFUN_FAST_NATIVE, SE_GROUP_HIDDEN, NULL, NULL },
      { "callback", js_callback, 0, JSFUN_FAST_NATIVE, SE_GROUP_ENGINE, N_("Javascript"), NULL },
      { "xine_cfg_get", js_xine_cfg_get, 0, JSFUN_FAST_NATIVE, SE_GROUP_PROPERTIES, NULL, NULL },
      { "xine_cfg_set", js_xine_cfg_set, 0, JSFUN_FAST_NATIVE, SE_GROUP_PROPERTIES, NULL, NULL },
      { NULL }
    };
    se_defuns (se, se->g, defs);
  }

  event_obj = se_create_object (se, NULL, "event", NULL, SE_GROUP_ENGINE,
				"stream_end=JS");
  se_prop_create (se, event_obj, "stream_end", "", SE_TYPE_STRING, 0, FALSE);

  js_icon_obj = se_create_object (se, NULL, "__js", NULL, SE_GROUP_HIDDEN, NULL);
  se_prop_create_int (se, js_icon_obj, "v", 0, TRUE);

  return se;
}

JSBool se_warn_initialisation (JSContext *cx, const char *func)
{
  JS_ReportWarning
    (cx, _("Attempt to call function “%s ()” during initialisation"), func);
  return JS_FALSE;
}
