/*
 * Copyright (C) 2001-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
 *
 * needful things
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>

#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>

#include "ui.h"
#include "utils.h"
#include "engine.h"
#include "http.h"
#include "player.h"
#include "playlist.h"
#include "gtkvideo.h"

int gxine_init_count = 0;

static int ensure_mkdir (const char *path, mode_t mode)
{
  struct stat st;
  if (stat (path, &st))
    return errno != ENOENT ? errno : (mkdir (path, mode) ? errno : 0);
  if (!S_ISDIR (st.st_mode))
    return mkdir (path, mode) ? errno : 0; /* should be an error */
  return 0;
}

int ensure_path_exists (char *path, int mode)
{
  char *sep, *start;

  start = path + (*path == '/');
  while  ((sep = strchr (start, '/')))
  {
    int ret;
    *sep = 0;
    ret = ensure_mkdir (path, mode);
    *sep = '/';
    if (ret)
      return ret;
    start = sep + 1;
  }

  return ensure_mkdir (path, mode); /* leave mode as is if already present */
}

static char *filename_to_mrl_type (const char *path, const char *type)
{
  /* assumption: type looks like "foo:" and is <= 6 characters long */
  size_t type_len = strlen (type);
  char *mrl = g_filename_to_uri (path, NULL, NULL);
  memmove (mrl + type_len, mrl + 6, strlen (mrl + 5));
  memcpy (mrl, type, type_len);
  return mrl;
}

static char *filename_to_dvd (const char *srcpath, struct stat *pst)
{
  struct stat st;
  char *path = strdup (srcpath);
  size_t length = strlen (path);
  char *mrl, *vts = NULL;

  if (!pst->st_mode)
  {
    /* initial stat failed, so see if we have .../title[.chapter] */
    mrl = strrchr (path, '/'); /* can't fail */
    int count = 0;
    if ((sscanf (mrl, "/%*[0-9].%*[0-9]%n", &count), !mrl[count]) ||
	(sscanf (mrl, "/%*[0-9]%n", &count), !mrl[count]))
    {
      *mrl = 0;
      length = mrl - path;
      if (stat (path, pst))
	pst->st_mode = 0;
    }
  }

  if (!S_ISDIR (pst->st_mode))
  {
    if (length >= 4 && !strcasecmp (path + length - 4, ".iso"))
      mrl = filename_to_mrl_type (srcpath, "dvd:");
    else
      mrl = NULL;
    free (path);
    return mrl;
  }

  while (--length && G_IS_DIR_SEPARATOR (path[length]))
    /**/;
  path[length + 1] = 0;

  vts = g_build_path (G_DIR_SEPARATOR_S, path, "VIDEO_TS", NULL);
  if (!stat (vts, &st) && S_ISDIR (st.st_mode))
    goto is_dvd;

  strcpy (vts + strlen (vts) - 8, "video_ts"); /* overwrite, lower case */
  if (!stat (vts, &st) && S_ISDIR (st.st_mode))
  {
  is_dvd:
    free (vts);
    free (path);
    return filename_to_mrl_type (srcpath, "dvd:");
  }

  return NULL;
}

static char *filename_to_vcd (const char *srcpath, struct stat *st)
{
  char *mrl, *path = strdup (srcpath);

  if (!st->st_mode)
  {
    /* initial stat failed, so see if we have ...@[EPST]?track */
    mrl = strrchr (path, '@'); /* can fail */
    if (mrl)
    {
      int count = 0;
      if ((sscanf (mrl, "@%*1[EePpSsTt]%*u%n", &count), !mrl[count]) ||
	  (sscanf (mrl, "@%*u%n", &count), !mrl[count]))
      {
	*mrl = 0;
	if (stat (path, st))
	  st->st_mode = 0;
      }
    }
  }

  if (S_ISDIR (st->st_mode))
  {
    free (path);
    return NULL;
  }

  const char *suffix = strrchr (path, '.');
  if (suffix && (!strcasecmp (suffix, ".cue") || !strcasecmp (suffix, ".nrg")))
  {
    free (path);
    return filename_to_mrl_type (srcpath, "vcd:");
  }

  free (path);
  return NULL;
}

static char *filename_to_uri (const char *path)
{
  struct stat st;
  char *mrl;

  if (stat (path, &st))
    st.st_mode = 0; /* invalid */

  if ((mrl = filename_to_dvd (path, &st)))
    return mrl;

  if ((mrl = filename_to_vcd (path, &st)))
    return mrl;

  return g_filename_to_uri (path, NULL, NULL);
}

char *make_path_uri (const char *path)
{
  if (g_path_is_absolute (path))
    return filename_to_uri (path);

  if ((*path & 0xDF) < 'A' || (*path & 0xDF) > 'Z')
    goto make_abs;

  const char *tmp = path + 1;
  for (; *tmp; ++tmp)
    if ((*tmp != '-' && *tmp < '0') || (*tmp > '9' && *tmp < 'A') ||
	(*tmp > 'Z' && *tmp < 'a') || *tmp > 'z')
      break;
  if (tmp[0] == ':' && tmp[1] == '/')
    return NULL; /* is a URI */

  make_abs:; /* is a relative path - make it absolute then a URI */
  char *dir = g_get_current_dir ();
  char *abspath = g_build_path (G_DIR_SEPARATOR_S, dir, path, NULL);
  free (dir);
  dir = filename_to_uri (abspath);
  free (abspath);
  return dir;
}

char *read_entire_file (const char *mrl, ssize_t *file_size)
{
  char        *buf;
  struct stat  statb;
  int          fd;
  ssize_t      size;

  if (!strncasecmp (mrl, "http://", 7))
    return http_download (mrl, file_size);

  if (file_size)
    *file_size = 0;

  if (stat (mrl, &statb) < 0)
  {
    /*g_print (_("utils: cannot stat '%s'\n"), mrl); */
    return NULL;
  }

  if (S_ISDIR (statb.st_mode))
  {
    errno = EISDIR;
    return NULL;
  }

  size = statb.st_size;

  fd = open (mrl, O_RDONLY);
  if (fd<0)
    return NULL;

  /* two extra bytes to ensure LF+NUL termination */
  buf = malloc (size + 2);
  if (!buf)
  {
    close (fd);
    errno = ENOMEM;
    return NULL;
  }

  size = read (fd, buf, size);
  if (size < 0)
  {
    /* read error */
    free (buf);
    size = errno;
    close (fd);
    errno = size;
    return NULL;
  }

  if (file_size)
    *file_size = size;

  if (size && buf[size - 1] != '\n')
    buf[size++] = '\n';
  buf[size] = 0;

  close (fd);

  return buf;
}

char *get_config_filename (const char *leaf)
{
  const char *cfgdir = g_get_user_config_dir ();
  int home_is_not_cfgdir = !!strcmp (g_get_home_dir (), cfgdir);
  return g_build_filename (cfgdir, ".gxine" + home_is_not_cfgdir, leaf, NULL);
}

FILE *open_write (const char *file, const char *errtitle)
{
  gchar *fname = g_strconcat (file, ".new", NULL);
  FILE *fd = NULL;
  if ((unlink (fname) && errno != ENOENT) || !(fd = fopen (fname, "w")))
    display_error_modal (FROM_GXINE, errtitle, _("Can't open file ‘%s’: %s"),
			 fname, strerror (errno));
  g_free (fname);
  return fd;
}

void close_write (const char *file, FILE *fd, const char *errtitle)
{
  gchar *fname = g_strconcat (file, ".new", NULL);
  int error;

  if ((error = ferror (fd)))
    display_error_modal (FROM_GXINE, errtitle,
			 _("Error while writing to ‘%s’: %s"),
			 fname, strerror (errno));
  if (fclose (fd))
  {
    display_error_modal (FROM_GXINE, errtitle,
			 _("Error when closing ‘%s’: %s"),
			 fname, strerror (errno));
    error = 1;
  }
  if (!error && rename (fname, file))
    display_error_modal (FROM_GXINE, errtitle,
			 _("Error when replacing ‘%s’: %s"),
			 file, strerror (errno));

  g_free (fname);
}

void window_show (GtkWidget *const widget, GtkWidget *const parent)
{
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_WINDOW (widget));
  g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
  gtk_window_set_transient_for ((GtkWindow *)widget,
				(GtkWindow *)(parent ? : app));
  gtk_widget_show_all (widget);
  gdk_window_raise (gtk_widget_get_window (widget));
}

void window_present (GtkWidget *const widget, GtkWidget *const parent)
{
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_WINDOW (widget));
  g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
  gtk_window_set_transient_for ((GtkWindow *)widget,
				(GtkWindow *)(parent ? : app));
  gtk_widget_show (widget);
  gdk_window_raise (gtk_widget_get_window (widget));
}

static void response_cb (GtkWidget *dialog, gint response, gpointer data)
{
  free (g_object_get_data (G_OBJECT (dialog), "msg"));
  if (!data)
    gtk_widget_destroy (dialog);
}

static struct {
  gchar *text;
  GtkWidget *dbox;
} combinable = { NULL, NULL };

GtkWidget *v_display_message (gxine_msg_source src, gboolean modal,
			      const gchar *primary, GtkMessageType type,
			      const gchar *fmt, va_list ap)
{
  static const char *const wtitle[][4] = {
    { N_("Message from gxine"),
      N_("Warning from gxine"),
      N_("Question from gxine"),
      N_("Error from gxine"),
    },
    { N_("Message from the xine engine"),
      N_("Warning from the xine engine"),
      N_("Question from the xine engine"),
      N_("Error from the xine engine"),
    },
    { N_("Message from the xine engine"),
      N_("Warning from the xine engine"),
      N_("Question from the xine engine"),
      N_("Error from the xine engine"),
    },
  };
  static const char *const ptitle[] = { "gxine", "xine-lib", "xine-lib" };
  static const uint8_t is_combinable[] = { FALSE, FALSE, TRUE };

  GtkWidget *dialog;
  gchar     *msg = g_strdup_vprintf (fmt, ap);

  switch (type)
  {
  case GTK_MESSAGE_WARNING:
    if (primary)
      g_printerr (_("%s: warning: %s: %s\n"), ptitle[src], primary, msg);
    else
      g_printerr (_("%s: warning: %s\n"), ptitle[src], msg);
    break;
  case GTK_MESSAGE_ERROR:
    if (primary)
      g_printerr (_("%s: error: %s: %s\n"), ptitle[src], primary, msg);
    else
      g_printerr (_("%s: error: %s\n"), ptitle[src], msg);
    break;
  default:;
  }

  gdk_threads_enter ();

  if (is_combinable[src] && combinable.dbox)
  {
    dialog = combinable.dbox;

    if (primary)
    {
      g_object_set (dialog, "message-type", type, "text", primary, NULL);
      gchar *qmsg = g_markup_escape_text (msg, -1);
      gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
						"%s\n\n%s", qmsg, combinable.text);
      free (qmsg);
    }
    else
    {
      g_object_set (dialog, "message-type", type, "text", msg, NULL);
      gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
						"%s", combinable.text);
    }

    gtk_window_set_focus_on_map (GTK_WINDOW (dialog), TRUE);
    gtk_window_reshow_with_initial_size (GTK_WINDOW (dialog));
  }
  else
  {
    dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT |
					   (modal ? GTK_DIALOG_MODAL : 0), type,
				     GTK_BUTTONS_CLOSE, "%s", primary ? : msg);
    if (primary)
      gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
						"%s", msg);
  }
  /* lose the 'combinable' details regardless */
  free (combinable.text);
  combinable.text = NULL;
  combinable.dbox = NULL;

  gtk_window_set_title (GTK_WINDOW (dialog), gettext (wtitle[src][type]));
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);

  g_signal_connect (G_OBJECT (dialog), "response",
		    G_CALLBACK (response_cb), modal ? response_cb : NULL);
  g_object_set_data (G_OBJECT (dialog), "msg", msg);
  window_show (dialog, NULL);

  if (modal)
  {
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
    dialog = NULL;
  }

  gdk_threads_leave ();

  return dialog;
}

void display_error_modal (gxine_msg_source src, const gchar *title,
			  const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, TRUE, title, GTK_MESSAGE_ERROR, fmt, ap);
  va_end (ap);
}

void display_error (gxine_msg_source src, const gchar *title,
		    const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, FALSE, title, GTK_MESSAGE_ERROR, fmt, ap);
  va_end (ap);
}

void display_warning (gxine_msg_source src, const gchar *title,
		      const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, FALSE, title, GTK_MESSAGE_WARNING, fmt, ap);
  va_end (ap);
}

void display_info (gxine_msg_source src, const gchar *title,
		   const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, FALSE, title, GTK_MESSAGE_INFO, fmt, ap);
  va_end (ap);
}

static void destroy_combinable (GtkWidget *dbox, gint which, gpointer data)
{
  if (dbox == combinable.dbox && data == combinable.text)
  {
    free (combinable.text);
    combinable.text = NULL;
    combinable.dbox = NULL;
  }
}

static void __attribute__ ((format (printf, 4, 0)))
v_display_combinable (gxine_msg_source src, const gchar *title,
				  GtkMessageType type, const gchar *fmt,
				  va_list ap)
{
  va_list apx;
  gchar *msg;
  va_copy (apx, ap);

  msg = g_strdup_vprintf (fmt, ap);

  if (msg) {

    if (title)
      asreprintf(&msg, "<b>%s</b>\n%s", title, msg);

    combinable.dbox = v_display_message (src, FALSE, title, type, fmt, apx);
    combinable.text = msg;
    g_signal_connect (G_OBJECT (combinable.dbox), "response",
                      G_CALLBACK (destroy_combinable), msg);
  }
  va_end (apx);
}

void display_error_combinable (gxine_msg_source src, const gchar *title,
			       const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_combinable (src, title, GTK_MESSAGE_ERROR, fmt, ap);
  va_end (ap);
}

void display_warning_combinable (gxine_msg_source src, const gchar *title,
				 const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_combinable (src, title, GTK_MESSAGE_WARNING, fmt, ap);
  va_end (ap);
}

void display_info_combinable (gxine_msg_source src, const gchar *title,
			      const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_combinable (src, title, GTK_MESSAGE_INFO, fmt, ap);
  va_end (ap);
}

void int_to_timestring (int int_time, char *string_time, gint length)
{
  int sign = int_time < 0;
  int_time = abs (int_time);
  if (int_time % 1000)
    snprintf (string_time, length, "%s%02d:%02d:%06.3lf",
	      sign ? "-" : "",
	      int_time / 3600000,
	      (int_time / 60000) % 60,
	      (int_time % 60000) / 1000.0);
  else
    snprintf (string_time, length, "%s%02d:%02d:%02.0lf",
	      sign ? "-" : "",
	      int_time / 3600000,
	      (int_time / 60000) % 60,
	      (int_time % 60000) / 1000.0);
}

int parse_timestring (const char *string_time)
{
  int hours = 0, minutes = 0;
  double seconds = 0;

  int sign = (*string_time == '-') ? -1 : 1;
  if (sign < 0)
    ++string_time;

  if (sscanf (string_time, "%d:%d:%lf", &hours, &minutes, &seconds) == 3)
    return sign * (hours * 3600000 + minutes * 60000 + seconds * 1000);

  if (sscanf (string_time, "%d:%lf", &minutes, &seconds) == 2)
    return sign * (minutes * 60000 + seconds * 1000);

  return 0;
}

static char *filepath = NULL;

static void set_start_dir (GtkWidget *dbox)
{
  if (!filepath)
  {
    xine_cfg_entry_t entry;
    if (xine_config_lookup_entry (xine, "media.files.origin_path", &entry)
	&& entry.type == XINE_CONFIG_TYPE_STRING)
      filepath = strdup (entry.str_value);
    else
      filepath = g_get_current_dir ();
  }
  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dbox), filepath);
}

static void update_start_dir (GtkWidget *dbox)
{
  gchar *dir = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dbox));
  if (dir)
  {
    free (filepath);
    filepath = dir;
  }
}

gchar *modal_file_dialog (const char *title, gboolean loading, gboolean local,
			  gboolean uri, const gchar *pattern, GtkWidget *extra,
			  GtkWidget *parent)
{
  gchar *ret = NULL;
  GtkWidget *dbox = gtk_file_chooser_dialog_new
	(title, NULL,
	 loading ? GTK_FILE_CHOOSER_ACTION_OPEN
		 : GTK_FILE_CHOOSER_ACTION_SAVE,
	 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
	 loading ? GTK_STOCK_OPEN : GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
	 NULL);
  GtkFileChooser *ch = GTK_FILE_CHOOSER (dbox);

  gtk_file_chooser_set_local_only (ch, local);
  gtk_file_chooser_set_select_multiple (ch, FALSE);
  gtk_file_chooser_set_extra_widget (ch, extra);
  gtk_file_chooser_set_do_overwrite_confirmation (ch, TRUE);

  set_start_dir (dbox);

  window_show (dbox, parent);
  if (gtk_dialog_run (GTK_DIALOG (dbox)) == GTK_RESPONSE_ACCEPT)
  {
    ret = uri ? gtk_file_chooser_get_uri (ch)
	      : gtk_file_chooser_get_filename (ch);
    update_start_dir (dbox);
  }

  gtk_file_chooser_set_extra_widget (ch, NULL);
  gtk_widget_destroy (dbox);
  return ret;
}

GSList *modal_multi_file_dialog (const char *title, gboolean local,
				 const gchar *pattern, GtkWidget *parent)
{
  GSList *ret = NULL;
  GtkWidget *dbox = gtk_file_chooser_dialog_new
        (title, NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
	 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
	 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
	 NULL);

  gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dbox), local);
  gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dbox), TRUE);
  set_start_dir (dbox);

  window_show (dbox, parent);
  if (gtk_dialog_run (GTK_DIALOG (dbox)) == GTK_RESPONSE_ACCEPT)
  {
    ret = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dbox));
    update_start_dir (dbox);
  }

  gtk_widget_destroy (dbox);
  return ret;
}

void get_mrl_from_filesystem (GtkWindow *dbox, GtkEntry *entry)
{
  gchar *file = modal_file_dialog (_("Select file for MRL"), TRUE, TRUE, TRUE,
				   NULL, NULL, (GtkWidget *)dbox);
  if (file)
  {
    gtk_widget_grab_focus (GTK_WIDGET (entry));

    const char *suffix = gtk_entry_get_text (entry);
    if (suffix)
    {
      while (isspace (*suffix))
	++suffix;
      suffix = (*suffix && *suffix != '/') ? strchr (suffix, '#') : NULL;
    }

    gchar *start = g_strconcat (file, suffix, NULL);
    gtk_entry_set_text (entry, start);
    gtk_editable_set_position (GTK_EDITABLE (entry), 0);

    g_free (start);
    g_free (file);
  }
}

static gboolean close_cb (GtkWidget *widget, gpointer data)
{
  if (data)
    *((gboolean *)data) = FALSE;
  gtk_widget_hide (widget);
  return TRUE;
}

void hide_on_delete (GtkWidget *widget, gboolean *visible)
{
  g_signal_connect (G_OBJECT(widget), "delete-event",
		    G_CALLBACK(close_cb), visible);
}

void utils_init (void)
{
  /* Nothing to do :-) */
}

void add_table_row_items (GtkWidget *table, gint row, gboolean expand,
			  const gchar *labeltext, ...)
{
  /* Pads out last widget to fill remaining space.
   * First widget is expandable horizontally, rest aren't.
   */
  int column = 1;
  GtkWidget *label = gtk_label_new (labeltext);
  GtkAttachOptions xattach = GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		   yattach = (expand ? GTK_EXPAND | GTK_SHRINK : 0) | GTK_FILL;
  va_list ap;

  gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1, 0, 0, 2, 2);
  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);

  va_start (ap, labeltext);
  GtkWidget *widget = va_arg (ap, GtkWidget *);
  while (widget)
  {
    guint ncols = 0;
    GtkWidget *next = va_arg (ap, GtkWidget *);
    gtk_table_get_size (GTK_TABLE (table), NULL, &ncols);
    gtk_table_attach (GTK_TABLE (table), widget, column,
                      next ? column + 1 : ncols,
                      row, row + 1, xattach, yattach, 2, 2);
    yattach = xattach = GTK_FILL;
    ++column;
    widget = next;
  }
  va_end (ap);
}

gchar *asreprintf (gchar **str, const char *fmt, ...)
{
  gchar *ret;
  va_list ap;

  va_start (ap, fmt);
  ret = g_strdup_vprintf (fmt, ap);
  va_end (ap);
  g_free (*str);
  return *str = ret;
}

#ifdef EXP_STUFF
char *unique_name (char *base)
{
  static int i = 0;
  return g_strdup_printf ("%s_%d", base, ++i);
}
#endif

void do_pending_events (void)
{
  while (gtk_events_pending ())
    gtk_main_iteration ();
}

const char *get_copyright_notice (void)
{
  static gchar *copyright = NULL;
  if (!copyright)
    copyright = g_strdup_printf (_("© 2002-%d the xine project team"),
				 2017);
  return copyright;
}

#include <xine/xine_internal.h>
#include <xine/configfile.h>
void config_update_default (const char *key, int value)
{
  /* warning: accesses xine internals */
  cfg_entry_t *entry = xine->config->lookup_entry (xine->config, key);
  pthread_mutex_lock(&xine->config->config_lock);
  if (entry)
    entry->num_default = value;
  pthread_mutex_unlock(&xine->config->config_lock);
}

/* List a subset of video plugins */

extern const char *const *xine_list_video_output_plugins_typed (xine_t *, uint64_t)
	__attribute__((__weak__));

const char *const *gxine_list_video_output_plugins (xine_t *xine)
{
  int types = 0;
#if !defined(HAVE_XCB) || defined(ENABLE_X11_VO)
  types |= 1 << XINE_VISUAL_TYPE_X11;
#endif
#ifdef HAVE_XCB
  types |= 1 << XINE_VISUAL_TYPE_XCB;
#endif
  return xine_check_version (1, 1, 12)
	 ? xine_list_video_output_plugins_typed (xine, types)
	 : xine_list_video_output_plugins (xine);
}
