diff options
Diffstat (limited to 'trunk/src')
109 files changed, 35351 insertions, 0 deletions
diff --git a/trunk/src/.cvsignore b/trunk/src/.cvsignore new file mode 100644 index 000000000..45129a42d --- /dev/null +++ b/trunk/src/.cvsignore @@ -0,0 +1,21 @@ +Makefile.in +Makefile +*bak +*gladep +bvw-test +disc-test +egg-* +baconvideowidget-marshal.* +list_v4l +test-parser +totemplparser-marshal.* +totem.desktop +totem-properties-page +totem-video-thumbnailer +totem +vanity +stamp-totem-pl-parser-builtins.h +totem-pl-parser-builtins.c +totem-pl-parser-builtins.h +test-properties-page +totem-video-indexer diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am new file mode 100644 index 000000000..a6afe86bf --- /dev/null +++ b/trunk/src/Makefile.am @@ -0,0 +1,404 @@ +SUBDIRS = backend plparse + +bin_PROGRAMS = totem totem-video-thumbnailer totem-video-indexer +libexec_PROGRAMS = +noinst_PROGRAMS = list_v4l disc-test +noinst_LTLIBRARIES = \ + libbaconpropertiespage.la \ + libbaconmessageconnection.la \ + libtotem_player.la + +common_defines = \ + -D_REENTRANT \ + -DDBUS_API_SUBJECT_TO_CHANGE \ + -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \ + -DGCONF_PREFIX=\""/apps/totem"\" \ + -DDATADIR=\""$(datadir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLOGO_PATH=DATADIR\"\"G_DIR_SEPARATOR_S\"totem\"G_DIR_SEPARATOR_S\"totem_logo.png\" \ + $(DISABLE_DEPRECATED) + +modules_flags = -export_dynamic -avoid-version -module + +# Bacon message connection ltlibrary + +BACON_MESSAGE_CONNECTION = \ + bacon-message-connection.c \ + bacon-message-connection.h + +libbaconmessageconnection_la_SOURCES = \ + $(BACON_MESSAGE_CONNECTION) + +libbaconmessageconnection_la_CPPFLAGS = \ + $(common_defines) \ + $(AM_CPPFLAGS) + +libbaconmessageconnection_la_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(AM_CFLAGS) + +libbaconmessageconnection_la_LDFLAGS = \ + $(AM_LDFLAGS) + + +BACON_VOLUME = \ + bacon-volume.c \ + bacon-volume.h + +BACON_V4L_SELECTION = \ + video-dev.c \ + video-dev.h \ + bacon-v4l-selection.c \ + bacon-v4l-selection.h + +EGGDIR=$(srcdir)/../../libegg/libegg/recent-files/ +BACONDIR=$(srcdir)/../../libbacon/src +regenerate-built-sources: + EGGFILES="$(BACON_MESSAGE_CONNECTION) $(BACON_VOLUME)" EGGDIR="$(BACONDIR)" $(srcdir)/update-from-egg.sh || true + +# Bacon properties page ltlibrary + +libbaconpropertiespage_la_SOURCES = \ + bacon-video-widget-properties.c \ + bacon-video-widget-properties.h \ + totem-interface.c \ + totem-interface.h + +libbaconpropertiespage_la_CPPFLAGS = \ + -I$(srcdir)/plparse \ + -I$(srcdir)/backend \ + $(common_defines) \ + $(AM_CPPFLAGS) + +libbaconpropertiespage_la_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(WARN_CFLAGS) \ + $(AM_CFLAGS) + +libbaconpropertiespage_la_LDFLAGS = \ + $(AM_LDFLAGS) + +# Totem UI ltlibrary + +libtotem_player_la_SOURCES = \ + totem-statusbar.c \ + totem-statusbar.h \ + $(BACON_VOLUME) + +libtotem_player_la_CPPFLAGS = \ + -I$(srcdir)/backend \ + $(common_defines) \ + $(AM_CPPFLAGS) + +libtotem_player_la_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(WARN_CFLAGS) \ + $(AM_CFLAGS) + +libtotem_player_la_LDFLAGS = \ + $(AM_LDFLAGS) + +# Totem + +totem_SOURCES = \ + totem.c totem.h \ + totem-remote.c totem-remote.h \ + totem-preferences.c totem-preferences.h \ + totem-private.h \ + totem-scrsaver.c \ + totem-scrsaver.h \ + totem-options.c totem-options.h \ + totem-playlist.c totem-playlist.h \ + totem-screenshot.c \ + totem-screenshot.h \ + totem-session.c totem-session.h \ + totem-sidebar.c totem-sidebar.h \ + totem-skipto.c totem-skipto.h \ + totem-menu.c totem-menu.h \ + totem-missing-plugins.c totem-missing-plugins.h \ + totem-time-label.c totem-time-label.h \ + totem-uri.c totem-uri.h \ + totem-gromit.c totem-gromit.h \ + ev-sidebar.c ev-sidebar.h \ + totem-subtitle-encoding.c \ + totem-subtitle-encoding.h \ + $(BUILT_SOURCES) + +totem_CPPFLAGS = \ + -I$(srcdir)/plparse \ + -I$(srcdir)/backend \ + -I$(top_builddir)/data \ + -I$(top_builddir)/src/plparse \ + $(common_defines) \ + $(AM_CPPFLAGS) + +totem_CFLAGS = \ + $(WARN_CFLAGS) \ + $(EXTRA_GNOME_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(MEDIA_PLAYER_KEYS_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +totem_LDFLAGS = \ + $(AM_LDFLAGS) + +totem_LDADD = \ + plparse/libtotem-plparser.la \ + backend/libbaconvideowidget.la \ + libbaconpropertiespage.la \ + libbaconmessageconnection.la \ + libtotem_player.la \ + $(EXTRA_GNOME_LIBS) \ + $(REMOTE_LIBS) \ + $(XVIDMODE_LIBS) \ + $(NVTV_LIBS) \ + $(DBUS_LIBS) \ + $(MEDIA_PLAYER_KEYS_LIBS) \ + $(XTEST_LIBS) \ + $(X_LIBS) \ + $(GIMME_CODEC_LIBS) + +BUILT_SOURCES = \ + totem-marshal.h \ + totem-marshal.c + +#Rule to generate the marshal files +totem-marshal.c: totem-marshal.list + @GLIB_GENMARSHAL@ --prefix=totem_marshal $< --header --body > $@ + +totem-marshal.h: totem-marshal.list + @GLIB_GENMARSHAL@ --prefix=totem_marshal $< --header > $@ + +# Totem video thumbnailer + +totem_video_thumbnailer_SOURCES = \ + totem-video-thumbnailer.c + +totem_video_thumbnailer_CPPFLAGS = \ + -I$(srcdir)/plparse \ + -I$(srcdir)/backend \ + $(common_defines) \ + $(AM_CPPFLAGS) + +totem_video_thumbnailer_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +totem_video_thumbnailer_LDFLAGS = \ + $(AM_LDFLAGS) + +totem_video_thumbnailer_LDADD = \ + backend/libbaconvideowidget.la \ + $(GTK_LIBS) \ + $(EXTRA_GNOME_LIBS) \ + $(NVTV_LIBS) \ + $(XTEST_LIBS) \ + $(XVIDMODE_LIBS) \ + $(X_LIBS) + +# Nautilus Property Page + +if HAVE_NAUTILUS + +nautilusdir = $(libdir)/nautilus/extensions-1.0 +nautilus_LTLIBRARIES = libtotem-properties-page.la + +libtotem_properties_page_la_SOURCES = \ + totem-properties-main.c \ + totem-properties-view.c \ + totem-properties-view.h + +libtotem_properties_page_la_CPPFLAGS = \ + -I$(srcdir)/backend \ + -I$(srcdir)/plparse \ + -I$(top_builddir)/data \ + $(common_defines) \ + $(AM_CPPFLAGS) + +libtotem_properties_page_la_CFLAGS = \ + $(WARN_CFLAGS) \ + $(EXTRA_GNOME_CFLAGS) \ + $(NAUTILUS_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +libtotem_properties_page_la_LDFLAGS = \ + $(modules_flags) \ + $(AM_LDFLAGS) + +libtotem_properties_page_la_LIBADD = \ + backend/libbaconvideowidget.la \ + libbaconpropertiespage.la \ + $(EXTRA_GNOME_LIBS) \ + $(NAUTILUS_LIBS) \ + $(NVTV_LIBS) \ + $(XTEST_LIBS) \ + $(XVIDMODE_LIBS) \ + $(X_LIBS) + + +noinst_PROGRAMS += test-properties-page + +test_properties_page_SOURCES = \ + totem-properties-main.c \ + totem-properties-view.c \ + totem-properties-view.h \ + test-properties-page.c + +test_properties_page_CPPFLAGS = \ + -I$(srcdir)/backend \ + -I$(srcdir)/plparse \ + -I$(top_builddir)/data \ + $(common_defines) \ + $(AM_CPPFLAGS) + +test_properties_page_CFLAGS = \ + $(WARN_CFLAGS) \ + $(EXTRA_GNOME_CFLAGS) \ + $(NAUTILUS_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +test_properties_page_LDFLAGS = \ + $(AM_LDFLAGS) + +test_properties_page_LDADD = \ + backend/libbaconvideowidget.la \ + libbaconpropertiespage.la \ + $(EXTRA_GNOME_LIBS) \ + $(NAUTILUS_LIBS) \ + $(NVTV_LIBS) \ + $(XTEST_LIBS) \ + $(XVIDMODE_LIBS) \ + $(X_LIBS) + +endif # HAVE_NAUTILUS + +# Vanity + +if TOTEM_VANITY + +bin_PROGRAMS += vanity + +vanity_SOURCES = \ + $(BACON_V4L_SELECTION) \ + totem-screenshot.c \ + totem-screenshot.h \ + vanity.c + +vanity_CPPFLAGS = \ + -I$(srcdir)/backend \ + -I$(srcdir)/plparse \ + $(common_defines) \ + $(AM_CPPFLAGS) + +vanity_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +vanity_LDFLAGS = \ + $(AM_LDFLAGS) + +vanity_LDADD = \ + backend/libbaconvideowidget.la \ + $(EXTRA_GNOME_LIBS) \ + $(NVTV_LIBS) \ + $(XTEST_LIBS) \ + $(XVIDMODE_LIBS) \ + $(X_LIBS) + +endif # TOTEM_VANITY + +# Totem Video Indexer + +totem_video_indexer_SOURCES = \ + totem-video-indexer.c + +totem_video_indexer_CPPFLAGS = \ + -I$(srcdir)/plparse \ + -I$(srcdir)/backend \ + -I$(top_builddir)/data \ + $(common_defines) \ + $(AM_CPPFLAGS) + +totem_video_indexer_CFLAGS = \ + $(WARN_CFLAGS) \ + $(EXTRA_GNOME_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +totem_video_indexer_LDFLAGS = \ + $(AM_LDFLAGS) + +totem_video_indexer_LDADD = \ + backend/libbaconvideowidget.la \ + $(EXTRA_GNOME_LIBS) \ + $(GTK_LIBS) \ + $(NVTV_LIBS) \ + $(XTEST_LIBS) \ + $(XVIDMODE_LIBS) \ + $(X_LIBS) + +# Tests + +disc_test_SOURCES = \ + disc-test.c + +disc_test_CPPFLAGS = \ + -I$(srcdir)/plparse \ + $(common_defines) \ + $(AM_CPPFLAGS) + +disc_test_CFLAGS = \ + $(WARN_CFLAGS) \ + $(GTK_CFLAGS) \ + $(EXTRA_GNOME_CFLAGS) \ + $(AM_CFLAGS) + +disc_test_LDFLAGS = \ + $(AM_LDFLAGS) + +disc_test_LDADD = \ + plparse/libtotem-plparser.la \ + $(GTK_LIBS) \ + $(EXTRA_GNOME_LIBS) + + +list_v4l_SOURCES = \ + list_v4l.c \ + $(BACON_V4L_SELECTION) + +list_v4l_CPPFLAGS = \ + $(common_defines) \ + $(AM_CPPFLAGS) + +list_v4l_CFLAGS = \ + $(WARN_CFLAGS) \ + $(GTK_CFLAGS) \ + $(AM_CFLAGS) + +list_v4l_LDFLAGS = \ + $(AM_LDFLAGS) + +list_v4l_LDADD = \ + $(GTK_LIBS) + + +CLEANFILES = \ + *.bak \ + core* \ + *.orig \ + *~ \ + $(desktop_DATA) \ + $(BUILT_SOURCES) + +EXTRA_DIST = \ + totem-marshal.list diff --git a/trunk/src/backend/.cvsignore b/trunk/src/backend/.cvsignore new file mode 100644 index 000000000..936612810 --- /dev/null +++ b/trunk/src/backend/.cvsignore @@ -0,0 +1,4 @@ +Makefile +Makefile.in +baconvideowidget-marshal.* +bvw-test diff --git a/trunk/src/backend/Makefile.am b/trunk/src/backend/Makefile.am new file mode 100644 index 000000000..39d2a6543 --- /dev/null +++ b/trunk/src/backend/Makefile.am @@ -0,0 +1,67 @@ +noinst_PROGRAMS = bvw-test + +noinst_LTLIBRARIES = libbaconvideowidget.la + +bvw_test_SOURCES = bvw-test.c + +bvw_test_CPPFLAGS = \ + -I$(srcdir)/../plparse \ + -DDATADIR=\"$(pkgdatadir)\" \ + -DLOGO_PATH=DATADIR\"\"G_DIR_SEPARATOR_S\"totem\"G_DIR_SEPARATOR_S\"totem_logo.png\" + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +bvw_test_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(AM_CFLAGS) + +bvw_test_LDADD = \ + libbaconvideowidget.la \ + $(GTK_LIBS) $(XVIDMODE_LIBS) $(NVTV_LIBS) $(XTEST_LIBS) $(X_LIBS) + +BVWMARSHALFILES = baconvideowidget-marshal.c baconvideowidget-marshal.h +GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0` +BUILT_SOURCES = $(BVWMARSHALFILES) + +baconvideowidget-marshal.h: baconvideowidget-marshal.list + ( $(GLIB_GENMARSHAL) --prefix=baconvideowidget_marshal $(srcdir)/baconvideowidget-marshal.list --header > baconvideowidget-marshal.h ) +baconvideowidget-marshal.c: baconvideowidget-marshal.h + ( $(GLIB_GENMARSHAL) --prefix=baconvideowidget_marshal $(srcdir)/baconvideowidget-marshal.list --body --header > baconvideowidget-marshal.c ) + +libbaconvideowidget_la_SOURCES = \ + $(BVWMARSHALFILES) \ + bacon-video-widget.h \ + bacon-video-widget-common.h bacon-video-widget-common.c \ + bacon-resize.h bacon-resize.c \ + video-utils.c video-utils.h \ + debug.h + +if TOTEM_GST +libbaconvideowidget_la_SOURCES += \ + bacon-video-widget-gst-0.10.c \ + gstscreenshot.c \ + gstscreenshot.h +else +libbaconvideowidget_la_SOURCES += \ + bacon-video-widget-xine.c +endif + +libbaconvideowidget_la_CPPFLAGS = \ + -I$(srcdir)/../plparse \ + -D_REENTRANT \ + -DGCONF_PREFIX=\""/apps/totem"\" \ + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +libbaconvideowidget_la_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(NVTV_CFLAGS) \ + $(AM_CFLAGS) + +libbaconvideowidget_la_LIBADD = \ + $(XVIDMODE_LIBS) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = \ + baconvideowidget-marshal.list diff --git a/trunk/src/backend/bacon-resize.c b/trunk/src/backend/bacon-resize.c new file mode 100644 index 000000000..3ddced71c --- /dev/null +++ b/trunk/src/backend/bacon-resize.c @@ -0,0 +1,157 @@ +/* bacon-resize.c + * Copyright (C) 2003-2004, Bastien Nocera <hadess@hadess.net> + * All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Cambridge, MA 02139, USA. + */ + +#include "config.h" +#include "bacon-resize.h" + +#ifdef HAVE_XVIDMODE +#include <glib.h> +#include <gdk/gdkx.h> +#include <gdk/gdk.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xlib.h> + +#include <X11/extensions/xf86vmode.h> +#include <X11/extensions/Xrandr.h> +#include <X11/extensions/Xrender.h> + +/* XVidMode */ +XF86VidModeModeInfo **modelines; +int nr_modeline; + +/* XRandR */ +XRRScreenConfiguration *xr_screen_conf; +XRRScreenSize *xr_sizes; +Rotation xr_current_rotation; +SizeID xr_original_size; +#endif + +gboolean +bacon_resize_init (void) +{ +#ifdef HAVE_XVIDMODE + int event_basep, error_basep, res; + + XLockDisplay (GDK_DISPLAY()); + + res = XF86VidModeQueryExtension (GDK_DISPLAY(), &event_basep, &error_basep) || !XRRQueryExtension (GDK_DISPLAY(), &event_basep, &error_basep); + + if (!res) { + XUnlockDisplay (GDK_DISPLAY()); + return FALSE; + } + + res = XF86VidModeGetAllModeLines (GDK_DISPLAY(), XDefaultScreen (GDK_DISPLAY()), &nr_modeline, &modelines); + + xr_screen_conf = XRRGetScreenInfo (GDK_DISPLAY(), GDK_ROOT_WINDOW()); + + XUnlockDisplay (GDK_DISPLAY()); + + return TRUE; + +#endif /* HAVE_XVIDMODE */ + return FALSE; +} + +void +bacon_resize (void) +{ +#ifdef HAVE_XVIDMODE + int width, height, i, xr_nsize; + XRRScreenSize *xr_sizes; + gboolean found = FALSE; + + XLockDisplay (GDK_DISPLAY()); + + /* Check if there's a viewport */ + width = gdk_screen_width (); + height = gdk_screen_height (); + if (width == modelines[0]->hdisplay + && height == modelines[0]->vdisplay) { + XUnlockDisplay (GDK_DISPLAY()); + return; + } + + gdk_error_trap_push (); + /* Find the xrandr mode that corresponds to the real size */ + xr_sizes = XRRConfigSizes (xr_screen_conf, &xr_nsize); + xr_original_size = XRRConfigCurrentConfiguration + (xr_screen_conf, &xr_current_rotation); + if (gdk_error_trap_pop ()) + g_warning ("XRRConfigSizes or XRRConfigCurrentConfiguration failed"); + + for (i = 0; i < xr_nsize; i++) { + if (modelines[0]->hdisplay == xr_sizes[i].width + && modelines[0]->vdisplay == xr_sizes[i].height) { + found = TRUE; + break; + } + } + + if (!found) { + XUnlockDisplay (GDK_DISPLAY()); + return; + } + gdk_error_trap_push (); + XRRSetScreenConfig (GDK_DISPLAY(), + xr_screen_conf, + GDK_ROOT_WINDOW(), + (SizeID) i, + xr_current_rotation, + CurrentTime); + gdk_flush (); + if (gdk_error_trap_pop ()) + g_warning ("XRRSetScreenConfig failed"); + + XUnlockDisplay (GDK_DISPLAY()); +#endif /* HAVE_XVIDMODE */ +} + +void +bacon_restore (void) +{ +#ifdef HAVE_XVIDMODE + int width, height; + + XLockDisplay (GDK_DISPLAY()); + + /* Check if there's a viewport */ + width = gdk_screen_width (); + height = gdk_screen_height (); + if (width == modelines[0]->hdisplay + && height == modelines[0]->vdisplay) { + XUnlockDisplay (GDK_DISPLAY()); + return; + } + gdk_error_trap_push (); + XRRSetScreenConfig (GDK_DISPLAY(), + xr_screen_conf, + GDK_ROOT_WINDOW(), + xr_original_size, + xr_current_rotation, + CurrentTime); + gdk_flush (); + if (gdk_error_trap_pop ()) + g_warning ("XRRSetScreenConfig failed"); + XUnlockDisplay (GDK_DISPLAY()); +#endif +} + diff --git a/trunk/src/backend/bacon-resize.h b/trunk/src/backend/bacon-resize.h new file mode 100644 index 000000000..547c98214 --- /dev/null +++ b/trunk/src/backend/bacon-resize.h @@ -0,0 +1,25 @@ +/* bacon-resize.h + * Copyright (C) 2003-2004, Bastien Nocera <hadess@hadess.net> + * All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Cambridge, MA 02139, USA. + */ + +#include <glib.h> + +gboolean bacon_resize_init (void); +void bacon_resize (void); +void bacon_restore (void); + diff --git a/trunk/src/backend/bacon-video-widget-common.c b/trunk/src/backend/bacon-video-widget-common.c new file mode 100644 index 000000000..247943318 --- /dev/null +++ b/trunk/src/backend/bacon-video-widget-common.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2006 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> + +#include "bacon-video-widget-common.h" + +struct { + int height; + int fps; +} vis_qualities[] = { + { 240, 15 }, /* VISUAL_SMALL */ + { 320, 25 }, /* VISUAL_NORMAL */ + { 480, 25 }, /* VISUAL_LARGE */ + { 600, 30 } /* VISUAL_EXTRA_LARGE */ +}; + +gboolean +bacon_video_widget_common_can_direct_seek (BaconVideoWidgetCommon *com) +{ + g_return_val_if_fail (com != NULL, FALSE); + + if (com->mrl == NULL) + return FALSE; + + /* (instant seeking only make sense with video, + * hence no cdda:// here) */ + if (g_str_has_prefix (com->mrl, "file://") || + g_str_has_prefix (com->mrl, "dvd://") || + g_str_has_prefix (com->mrl, "vcd://")) + return TRUE; + + return FALSE; +} + +gboolean +bacon_video_widget_common_get_vis_quality (VisualsQuality q, + int *height, int *fps) +{ + g_return_val_if_fail (height != NULL, FALSE); + g_return_val_if_fail (fps != NULL, FALSE); + g_return_val_if_fail (q < G_N_ELEMENTS (vis_qualities), FALSE); + + *height = vis_qualities[q].height; + *fps = vis_qualities[q].fps; + return TRUE; +} + diff --git a/trunk/src/backend/bacon-video-widget-common.h b/trunk/src/backend/bacon-video-widget-common.h new file mode 100644 index 000000000..4ba8239ab --- /dev/null +++ b/trunk/src/backend/bacon-video-widget-common.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2006 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef HAVE_BACON_VIDEO_WIDGET_COMMON_H +#define HAVE_BACON_VIDEO_WIDGET_COMMON_H + +#include "bacon-video-widget.h" +#include <glib.h> + +G_BEGIN_DECLS + +#define SMALL_STREAM_WIDTH 200 +#define SMALL_STREAM_HEIGHT 120 + +struct BaconVideoWidgetCommon { + char *mrl; +}; + +gboolean bacon_video_widget_common_can_direct_seek (BaconVideoWidgetCommon *com); +gboolean bacon_video_widget_common_get_vis_quality (VisualsQuality q, + int *height, + int *fps); + +G_END_DECLS + +#endif /* HAVE_BACON_VIDEO_WIDGET_COMMON_H */ diff --git a/trunk/src/backend/bacon-video-widget-gst-0.10.c b/trunk/src/backend/bacon-video-widget-gst-0.10.c new file mode 100644 index 000000000..0e4d599cb --- /dev/null +++ b/trunk/src/backend/bacon-video-widget-gst-0.10.c @@ -0,0 +1,5150 @@ +/* + * Copyright (C) 2003-2007 the GStreamer project + * Julien Moutte <julien@moutte.net> + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Tim-Philipp Müller <tim centricular net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission is above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add exemption clause. + * See license_change file for details. + * + */ + +#include <config.h> + +#ifdef HAVE_NVTV +#include <nvtv_simple.h> +#endif + +#include <gst/gst.h> + +/* GStreamer Interfaces */ +#include <gst/interfaces/xoverlay.h> +#include <gst/interfaces/navigation.h> +#include <gst/interfaces/colorbalance.h> +/* for detecting sources of errors */ +#include <gst/video/gstvideosink.h> +#include <gst/video/video.h> +#include <gst/audio/gstbaseaudiosink.h> +/* for pretty multichannel strings */ +#include <gst/audio/multichannel.h> + +#if 0 +/* for missing decoder/demuxer detection */ +#include <gst/utils/base-utils.h> +#endif + +/* system */ +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <stdio.h> + +/* gtk+/gnome */ +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gconf/gconf-client.h> + +#include "bacon-video-widget.h" +#include "bacon-video-widget-common.h" +#include "baconvideowidget-marshal.h" +#include "video-utils.h" +#include "gstscreenshot.h" +#include "bacon-resize.h" + +#define DEFAULT_HEIGHT 420 +#define DEFAULT_WIDTH 315 + +#define is_error(e, d, c) \ + (e->domain == GST_##d##_ERROR && \ + e->code == GST_##d##_ERROR_##c) + +/* Signals */ +enum +{ + SIGNAL_ERROR, + SIGNAL_EOS, + SIGNAL_REDIRECT, + SIGNAL_TITLE_CHANGE, + SIGNAL_CHANNELS_CHANGE, + SIGNAL_TICK, + SIGNAL_GOT_METADATA, + SIGNAL_BUFFERING, + SIGNAL_MISSING_PLUGINS, + LAST_SIGNAL +}; + +/* Properties */ +enum +{ + PROP_0, + PROP_LOGO_MODE, + PROP_POSITION, + PROP_CURRENT_TIME, + PROP_STREAM_LENGTH, + PROP_PLAYING, + PROP_SEEKABLE, + PROP_SHOWCURSOR, + PROP_MEDIADEV, + PROP_SHOW_VISUALS, + PROP_VOLUME +}; + +static const gchar *video_props_str[4] = { + GCONF_PREFIX "/brightness", + GCONF_PREFIX "/contrast", + GCONF_PREFIX "/saturation", + GCONF_PREFIX "/hue" +}; + +struct BaconVideoWidgetPrivate +{ + BaconVideoWidgetAspectRatio ratio_type; + + GstElement *play; + GstXOverlay *xoverlay; /* protect with lock */ + GstColorBalance *balance; /* protect with lock */ + GMutex *lock; + + guint update_id; + + GdkPixbuf *logo_pixbuf; + + gboolean media_has_video; + gboolean media_has_audio; + gint seekable; /* -1 = don't know, FALSE = no */ + gint64 stream_length; + gint64 current_time_nanos; + gint64 current_time; + gfloat current_position; + + GstTagList *tagcache; + GstTagList *audiotags; + GstTagList *videotags; + + gboolean got_redirect; + + GdkWindow *video_window; + GtkAllocation video_window_allocation; + + /* Visual effects */ + GList *vis_plugins_list; + gboolean show_vfx; + gboolean vis_changed; + VisualsQuality visq; + gchar *vis_element_name; + GstElement *audio_capsfilter; + + /* Other stuff */ + gint xpos, ypos; + gboolean logo_mode; + gboolean cursor_shown; + gboolean fullscreen_mode; + gboolean auto_resize; + gboolean have_xvidmode; + gboolean uses_fakesink; + + gint video_width; /* Movie width */ + gint video_height; /* Movie height */ + const GValue *movie_par; /* Movie pixel aspect ratio */ + gint video_width_pixels; /* Scaled movie width */ + gint video_height_pixels; /* Scaled movie height */ + gint video_fps_n; + gint video_fps_d; + + guint init_width; + guint init_height; + + gchar *media_device; + + BaconVideoWidgetAudioOutType speakersetup; + TvOutType tv_out_type; + gint connection_speed; + + GstMessageType ignore_messages_mask; + + GConfClient *gc; + + GstBus *bus; + gulong sig_bus_sync; + gulong sig_bus_async; + + BvwUseType use_type; + + gint eos_id; + + /* state we want to be in, as opposed to actual pipeline state + * which may change asynchronously or during buffering */ + GstState target_state; + gboolean buffering; + + /* for easy codec installation */ + GList *missing_plugins; /* GList of GstMessages */ + gboolean plugin_install_in_progress; +}; + +static void bacon_video_widget_set_property (GObject * object, + guint property_id, + const GValue * value, + GParamSpec * pspec); +static void bacon_video_widget_get_property (GObject * object, + guint property_id, + GValue * value, + GParamSpec * pspec); + +static void bacon_video_widget_finalize (GObject * object); + +static void bvw_update_interface_implementations (BaconVideoWidget *bvw); +static void setup_vis (BaconVideoWidget * bvw); +static GList * get_visualization_features (void); +static gboolean bacon_video_widget_configure_event (GtkWidget *widget, + GdkEventConfigure *event, BaconVideoWidget *bvw); +static void size_changed_cb (GdkScreen *screen, BaconVideoWidget *bvw); +static void bvw_process_pending_tag_messages (BaconVideoWidget * bvw); +static void bvw_stop_play_pipeline (BaconVideoWidget * bvw); +static GError* bvw_error_from_gst_error (BaconVideoWidget *bvw, GstMessage *m); + +static GtkWidgetClass *parent_class = NULL; + +static int bvw_signals[LAST_SIGNAL] = { 0 }; + +GST_DEBUG_CATEGORY (_totem_gst_debug_cat); +#define GST_CAT_DEFAULT _totem_gst_debug_cat + +/* FIXME: temporary utility functions so we don't have to up the GStreamer + * requirements to core/base CVS (0.10.11.1) before the next totem release */ +#define gst_base_utils_init() /* noop */ +#define gst_is_missing_plugin_message(msg) \ + bvw_is_missing_plugin_message(msg) +#define gst_missing_plugin_message_get_description \ + bvw_missing_plugin_message_get_description +#define gst_missing_plugin_message_get_installer_detail \ + bvw_missing_plugin_message_get_installer_detail + +static gboolean +bvw_is_missing_plugin_message (GstMessage * msg) +{ + g_return_val_if_fail (msg != NULL, FALSE); + g_return_val_if_fail (GST_IS_MESSAGE (msg), FALSE); + + if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_ELEMENT || msg->structure == NULL) + return FALSE; + + return gst_structure_has_name (msg->structure, "missing-plugin"); +} + +static gchar * +bvw_missing_plugin_message_get_description (GstMessage * msg) +{ + g_return_val_if_fail (bvw_is_missing_plugin_message (msg), NULL); + + return g_strdup (gst_structure_get_string (msg->structure, "name")); +} + +static gchar * +bvw_missing_plugin_message_get_installer_detail (GstMessage * msg) +{ + const GValue *val; + const gchar *type; + gchar *desc, *ret, *details; + + g_return_val_if_fail (bvw_is_missing_plugin_message (msg), NULL); + + type = gst_structure_get_string (msg->structure, "type"); + g_return_val_if_fail (type != NULL, NULL); + val = gst_structure_get_value (msg->structure, "detail"); + g_return_val_if_fail (val != NULL, NULL); + if (G_VALUE_HOLDS (val, GST_TYPE_CAPS)) { + details = gst_caps_to_string (gst_value_get_caps (val)); + } else if (G_VALUE_HOLDS (val, G_TYPE_STRING)) { + details = g_value_dup_string (val); + } else { + g_return_val_if_reached (NULL); + } + desc = bvw_missing_plugin_message_get_description (msg); + ret = g_strdup_printf ("gstreamer.net|0.10|totem|%s|%s-%s", + (desc) ? desc : "", type, (details) ? details: ""); + g_free (desc); + g_free (details); + return ret; +} + +typedef gchar * (* MsgToStrFunc) (GstMessage * msg); + +static gchar ** +bvw_get_missing_plugins_foo (const GList * missing_plugins, MsgToStrFunc func) +{ + GPtrArray *arr = g_ptr_array_new (); + + while (missing_plugins != NULL) { + g_ptr_array_add (arr, func (GST_MESSAGE (missing_plugins->data))); + missing_plugins = missing_plugins->next; + } + g_ptr_array_add (arr, NULL); + return (gchar **) g_ptr_array_free (arr, FALSE); +} + +static gchar ** +bvw_get_missing_plugins_details (const GList * missing_plugins) +{ + return bvw_get_missing_plugins_foo (missing_plugins, + gst_missing_plugin_message_get_installer_detail); +} + +static gchar ** +bvw_get_missing_plugins_descriptions (const GList * missing_plugins) +{ + return bvw_get_missing_plugins_foo (missing_plugins, + gst_missing_plugin_message_get_description); +} + +static void +bvw_clear_missing_plugins_messages (BaconVideoWidget * bvw) +{ + g_list_foreach (bvw->priv->missing_plugins, + (GFunc) gst_mini_object_unref, NULL); + g_list_free (bvw->priv->missing_plugins); + bvw->priv->missing_plugins = NULL; +} + +static void +bvw_check_if_video_decoder_is_missing (BaconVideoWidget * bvw) +{ + GList *l; + + if (bvw->priv->media_has_video || bvw->priv->missing_plugins == NULL) + return; + + for (l = bvw->priv->missing_plugins; l != NULL; l = l->next) { + GstMessage *msg = GST_MESSAGE (l->data); + gchar *d, *f; + + if ((d = gst_missing_plugin_message_get_installer_detail (msg))) { + if ((f = strstr (d, "|decoder-")) && strstr (f, "video")) { + GError *err; + + /* create a fake GStreamer error so we get a nice warning message */ + err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, "x"); + msg = gst_message_new_error (GST_OBJECT (bvw->priv->play), err, NULL); + g_error_free (err); + err = bvw_error_from_gst_error (bvw, msg); + gst_message_unref (msg); + g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0, err->message, FALSE, FALSE); + g_error_free (err); + g_free (d); + break; + } + g_free (d); + } + } +} + +static void +bvw_error_msg_print_dbg (GstMessage * msg) +{ + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_error (msg, &err, &dbg); + if (err) { + GST_ERROR ("error message = %s", GST_STR_NULL (err->message)); + GST_ERROR ("error domain = %d (%s)", err->domain, + GST_STR_NULL (g_quark_to_string (err->domain))); + GST_ERROR ("error code = %d", err->code); + GST_ERROR ("error debug = %s", GST_STR_NULL (dbg)); + GST_ERROR ("error source = %" GST_PTR_FORMAT, msg->src); + + g_message ("Error: %s\n%s\n", GST_STR_NULL (err->message), + GST_STR_NULL (dbg)); + + g_error_free (err); + } + g_free (dbg); +} + +static void +get_media_size (BaconVideoWidget *bvw, gint *width, gint *height) +{ + if (bvw->priv->logo_mode) { + if (bvw->priv->logo_pixbuf) { + *width = gdk_pixbuf_get_width (bvw->priv->logo_pixbuf); + *height = gdk_pixbuf_get_height (bvw->priv->logo_pixbuf); + } else { + *width = 0; + *height = 0; + } + } else { + if (bvw->priv->media_has_video) { + GValue * disp_par = NULL; + guint movie_par_n, movie_par_d, disp_par_n, disp_par_d, num, den; + + /* Create and init the fraction value */ + disp_par = g_new0 (GValue, 1); + g_value_init (disp_par, GST_TYPE_FRACTION); + + /* Square pixel is our default */ + gst_value_set_fraction (disp_par, 1, 1); + + /* Now try getting display's pixel aspect ratio */ + if (bvw->priv->xoverlay) { + GObjectClass *klass; + GParamSpec *pspec; + + klass = G_OBJECT_GET_CLASS (bvw->priv->xoverlay); + pspec = g_object_class_find_property (klass, "pixel-aspect-ratio"); + + if (pspec != NULL) { + GValue disp_par_prop = { 0, }; + + g_value_init (&disp_par_prop, pspec->value_type); + g_object_get_property (G_OBJECT (bvw->priv->xoverlay), + "pixel-aspect-ratio", &disp_par_prop); + + if (!g_value_transform (&disp_par_prop, disp_par)) { + GST_WARNING ("Transform failed, assuming pixel-aspect-ratio = 1/1"); + gst_value_set_fraction (disp_par, 1, 1); + } + + g_value_unset (&disp_par_prop); + } + } + + disp_par_n = gst_value_get_fraction_numerator (disp_par); + disp_par_d = gst_value_get_fraction_denominator (disp_par); + + GST_DEBUG ("display PAR is %d/%d", disp_par_n, disp_par_d); + + /* If movie pixel aspect ratio is enforced, use that */ + if (bvw->priv->ratio_type != BVW_RATIO_AUTO) { + switch (bvw->priv->ratio_type) { + case BVW_RATIO_SQUARE: + movie_par_n = 1; + movie_par_d = 1; + break; + case BVW_RATIO_FOURBYTHREE: + movie_par_n = 4 * bvw->priv->video_height; + movie_par_d = 3 * bvw->priv->video_width; + break; + case BVW_RATIO_ANAMORPHIC: + movie_par_n = 16 * bvw->priv->video_height; + movie_par_d = 9 * bvw->priv->video_width; + break; + case BVW_RATIO_DVB: + movie_par_n = 20 * bvw->priv->video_height; + movie_par_d = 9 * bvw->priv->video_width; + break; + /* handle these to avoid compiler warnings */ + case BVW_RATIO_AUTO: + default: + movie_par_n = 0; + movie_par_d = 0; + g_assert_not_reached (); + } + } + else { + /* Use the movie pixel aspect ratio if any */ + if (bvw->priv->movie_par) { + movie_par_n = gst_value_get_fraction_numerator (bvw->priv->movie_par); + movie_par_d = + gst_value_get_fraction_denominator (bvw->priv->movie_par); + } + else { + /* Square pixels */ + movie_par_n = 1; + movie_par_d = 1; + } + } + + GST_DEBUG ("movie PAR is %d/%d", movie_par_n, movie_par_d); + + if (!gst_video_calculate_display_ratio (&num, &den, + bvw->priv->video_width, bvw->priv->video_height, + movie_par_n, movie_par_d, disp_par_n, disp_par_d)) { + GST_WARNING ("overflow calculating display aspect ratio!"); + num = 1; /* FIXME: what values to use here? */ + den = 1; + } + + GST_DEBUG ("calculated scaling ratio %d/%d for video %dx%d", num, den, + bvw->priv->video_width, bvw->priv->video_height); + + /* now find a width x height that respects this display ratio. + * prefer those that have one of w/h the same as the incoming video + * using wd / hd = num / den */ + + /* start with same height, because of interlaced video */ + /* check hd / den is an integer scale factor, and scale wd with the PAR */ + if (bvw->priv->video_height % den == 0) { + GST_DEBUG ("keeping video height"); + bvw->priv->video_width_pixels = + (guint) gst_util_uint64_scale (bvw->priv->video_height, num, den); + bvw->priv->video_height_pixels = bvw->priv->video_height; + } else if (bvw->priv->video_width % num == 0) { + GST_DEBUG ("keeping video width"); + bvw->priv->video_width_pixels = bvw->priv->video_width; + bvw->priv->video_height_pixels = + (guint) gst_util_uint64_scale (bvw->priv->video_width, den, num); + } else { + GST_DEBUG ("approximating while keeping video height"); + bvw->priv->video_width_pixels = + (guint) gst_util_uint64_scale (bvw->priv->video_height, num, den); + bvw->priv->video_height_pixels = bvw->priv->video_height; + } + GST_DEBUG ("scaling to %dx%d", bvw->priv->video_width_pixels, + bvw->priv->video_height_pixels); + + *width = bvw->priv->video_width_pixels; + *height = bvw->priv->video_height_pixels; + + /* Free the PAR fraction */ + g_value_unset (disp_par); + g_free (disp_par); + } + else { + *width = 0; + *height = 0; + } + } +} + +static void +bacon_video_widget_realize (GtkWidget * widget) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + GdkWindowAttr attributes; + gint attributes_mask, w, h; + GdkColor colour; + + /* Creating our widget's window */ + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_KEY_PRESS_MASK; + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, widget); + + /* Creating our video window */ + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = 0; + attributes.y = 0; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_KEY_PRESS_MASK; + attributes_mask = GDK_WA_X | GDK_WA_Y; + + bvw->priv->video_window = gdk_window_new (widget->window, + &attributes, attributes_mask); + gdk_window_set_user_data (bvw->priv->video_window, widget); + + gdk_color_parse ("black", &colour); + gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, &colour); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + + gdk_window_set_background (bvw->priv->video_window, &colour); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + /* Connect to configure event on the top level window */ + g_signal_connect (G_OBJECT (gtk_widget_get_toplevel (widget)), + "configure-event", G_CALLBACK (bacon_video_widget_configure_event), bvw); + + /* get screen size changes */ + g_signal_connect (G_OBJECT (gtk_widget_get_screen (widget)), + "size-changed", G_CALLBACK (size_changed_cb), bvw); + + /* nice hack to show the logo fullsize, while still being resizable */ + get_media_size (BACON_VIDEO_WIDGET (widget), &w, &h); + totem_widget_set_preferred_size (widget, w, h); + +#ifdef HAVE_NVTV + if (!(nvtv_simple_init() && nvtv_enable_autoresize(TRUE))) { + nvtv_simple_enable(FALSE); + } +#endif + + bvw->priv->have_xvidmode = bacon_resize_init (); +} + +static void +bacon_video_widget_unrealize (GtkWidget *widget) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + +#ifdef HAVE_NVTV + /* Kill the TV out */ + nvtv_simple_exit(); +#endif + + gdk_window_set_user_data (bvw->priv->video_window, NULL); + gdk_window_destroy (bvw->priv->video_window); + bvw->priv->video_window = NULL; + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static void +bacon_video_widget_show (GtkWidget *widget) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + + if (widget->window) + gdk_window_show (widget->window); + if (bvw->priv->video_window) + gdk_window_show (bvw->priv->video_window); + + if (GTK_WIDGET_CLASS (parent_class)->show) + GTK_WIDGET_CLASS (parent_class)->show (widget); +} + +static void +bacon_video_widget_hide (GtkWidget *widget) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + + if (widget->window) + gdk_window_hide (widget->window); + if (bvw->priv->video_window) + gdk_window_hide (bvw->priv->video_window); + + if (GTK_WIDGET_CLASS (parent_class)->hide) + GTK_WIDGET_CLASS (parent_class)->hide (widget); +} + +static gboolean +bacon_video_widget_configure_event (GtkWidget *widget, GdkEventConfigure *event, + BaconVideoWidget *bvw) +{ + GstXOverlay *xoverlay = NULL; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + + xoverlay = bvw->priv->xoverlay; + + if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) { + gst_x_overlay_expose (xoverlay); + } + + return FALSE; +} + +static void +size_changed_cb (GdkScreen *screen, BaconVideoWidget *bvw) +{ + /* FIXME */ +} + +static gboolean +bacon_video_widget_expose_event (GtkWidget *widget, GdkEventExpose *event) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + GstXOverlay *xoverlay; + gboolean draw_logo; + XID window; + + if (event && event->count > 0) + return TRUE; + + g_mutex_lock (bvw->priv->lock); + xoverlay = bvw->priv->xoverlay; + if (xoverlay == NULL) { + bvw_update_interface_implementations (bvw); + xoverlay = bvw->priv->xoverlay; + } + if (xoverlay != NULL) + gst_object_ref (xoverlay); + + g_mutex_unlock (bvw->priv->lock); + + window = GDK_WINDOW_XWINDOW (bvw->priv->video_window); + + if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) + gst_x_overlay_set_xwindow_id (xoverlay, window); + + /* Start with a nice black canvas */ + gdk_draw_rectangle (widget->window, widget->style->black_gc, TRUE, 0, 0, + widget->allocation.width, widget->allocation.height); + + /* if there's only audio and no visualisation, draw the logo as well */ + draw_logo = bvw->priv->media_has_audio && + !bvw->priv->media_has_video && !bvw->priv->show_vfx; + + if (bvw->priv->logo_mode || draw_logo) { + if (bvw->priv->logo_pixbuf != NULL) { + /* draw logo here */ + GdkPixbuf *logo = NULL; + gint s_width, s_height, w_width, w_height; + gfloat ratio; + + s_width = gdk_pixbuf_get_width (bvw->priv->logo_pixbuf); + s_height = gdk_pixbuf_get_height (bvw->priv->logo_pixbuf); + w_width = widget->allocation.width; + w_height = widget->allocation.height; + + if ((gfloat) w_width / s_width > (gfloat) w_height / s_height) { + ratio = (gfloat) w_height / s_height; + } else { + ratio = (gfloat) w_width / s_width; + } + + s_width *= ratio; + s_height *= ratio; + + if (s_width <= 1 || s_height <= 1) { + if (xoverlay != NULL) + gst_object_unref (xoverlay); + return TRUE; + } + + logo = gdk_pixbuf_scale_simple (bvw->priv->logo_pixbuf, + s_width, s_height, GDK_INTERP_BILINEAR); + + gdk_draw_pixbuf (widget->window, widget->style->fg_gc[0], logo, + 0, 0, (w_width - s_width) / 2, (w_height - s_height) / 2, + s_width, s_height, GDK_RGB_DITHER_NONE, 0, 0); + + gdk_pixbuf_unref (logo); + } else if (widget->window) { + /* No pixbuf, just draw a black background then */ + gdk_draw_rectangle (widget->window, widget->style->black_gc, + TRUE, 0, 0, + widget->allocation.width, + widget->allocation.height); + } + } else { + /* no logo, pass the expose to gst */ + if (xoverlay != NULL && GST_IS_X_OVERLAY (xoverlay)) + gst_x_overlay_expose (xoverlay); + else { + /* No xoverlay to expose yet */ + gdk_draw_rectangle (bvw->priv->video_window, widget->style->black_gc, + TRUE, 0, 0, + bvw->priv->video_window_allocation.width, + bvw->priv->video_window_allocation.height); + } + } + if (xoverlay != NULL) + gst_object_unref (xoverlay); + + return TRUE; +} + +/* need to use gstnavigation interface for these vmethods, to allow for the sink + to map screen coordinates to video coordinates in the presence of e.g. + hardware scaling */ + +static gboolean +bacon_video_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event) +{ + gboolean res = FALSE; + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + + g_return_val_if_fail (bvw->priv->play != NULL, FALSE); + + if (!bvw->priv->logo_mode) { + GstElement *videosink = NULL; + + g_object_get (bvw->priv->play, "video-sink", &videosink, NULL); + + if (videosink && GST_IS_BIN (videosink)) { + GstElement *newvideosink; + newvideosink = gst_bin_get_by_interface (GST_BIN (videosink), + GST_TYPE_NAVIGATION); + gst_object_unref (videosink); + videosink = newvideosink; + } + + if (videosink && GST_IS_NAVIGATION (videosink)) { + GstNavigation *nav = GST_NAVIGATION (videosink); + + gst_navigation_send_mouse_event (nav, "mouse-move", 0, event->x, event->y); + + res = TRUE; + } + + if (videosink) + gst_object_unref (videosink); + } + + if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event) + res |= GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event); + + return res; +} + +static gboolean +bacon_video_widget_button_press (GtkWidget *widget, GdkEventButton *event) +{ + gboolean res = FALSE; + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + + g_return_val_if_fail (bvw->priv->play != NULL, FALSE); + + if (!bvw->priv->logo_mode) { + GstElement *videosink = NULL; + + g_object_get (bvw->priv->play, "video-sink", &videosink, NULL); + + if (videosink && GST_IS_BIN (videosink)) { + GstElement *newvideosink; + newvideosink = gst_bin_get_by_interface (GST_BIN (videosink), + GST_TYPE_NAVIGATION); + gst_object_unref (videosink); + videosink = newvideosink; + } + + if (videosink && GST_IS_NAVIGATION (videosink)) { + GstNavigation *nav = GST_NAVIGATION (videosink); + + gst_navigation_send_mouse_event (nav, + "mouse-button-press", event->button, event->x, event->y); + + /* FIXME need to check whether the backend will have handled + * the button press + res = TRUE; */ + } + + if (videosink) + gst_object_unref (videosink); + } + + if (GTK_WIDGET_CLASS (parent_class)->button_press_event) + res |= GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); + + return res; +} + +static gboolean +bacon_video_widget_button_release (GtkWidget *widget, GdkEventButton *event) +{ + gboolean res = FALSE; + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + + g_return_val_if_fail (bvw->priv->play != NULL, FALSE); + + if (!bvw->priv->logo_mode) { + GstElement *videosink = NULL; + + g_object_get (bvw->priv->play, "video-sink", &videosink, NULL); + + if (videosink && GST_IS_BIN (videosink)) { + GstElement *newvideosink; + newvideosink = gst_bin_get_by_interface (GST_BIN (videosink), + GST_TYPE_NAVIGATION); + gst_object_unref (videosink); + videosink = newvideosink; + } + + if (videosink && GST_IS_NAVIGATION (videosink)) { + GstNavigation *nav = GST_NAVIGATION (videosink); + + gst_navigation_send_mouse_event (nav, + "mouse-button-release", event->button, event->x, event->y); + + res = TRUE; + } + + if (videosink) + gst_object_unref (videosink); + } + + if (GTK_WIDGET_CLASS (parent_class)->button_release_event) + res |= GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event); + + return res; +} + +static void +bacon_video_widget_size_request (GtkWidget * widget, + GtkRequisition * requisition) +{ + requisition->width = 240; + requisition->height = 180; +} + +static void +bacon_video_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget); + + g_return_if_fail (widget != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (widget)); + + widget->allocation = *allocation; + + if (GTK_WIDGET_REALIZED (widget)) { + gfloat width, height, ratio; + int w, h; + + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + /* resize video_window */ + get_media_size (bvw, &w, &h); + if (!w || !h) { + w = allocation->width; + h = allocation->height; + } + width = w; + height = h; + + if ((gfloat) allocation->width / width > + (gfloat) allocation->height / height) { + ratio = (gfloat) allocation->height / height; + } else { + ratio = (gfloat) allocation->width / width; + } + + width *= ratio; + height *= ratio; + + bvw->priv->video_window_allocation.width = width; + bvw->priv->video_window_allocation.height = height; + bvw->priv->video_window_allocation.x = (allocation->width - width) / 2; + bvw->priv->video_window_allocation.y = (allocation->height - height) / 2; + gdk_window_move_resize (bvw->priv->video_window, + (allocation->width - width) / 2, + (allocation->height - height) / 2, + width, height); + gtk_widget_queue_draw (widget); + } +} + +static gboolean +bvw_boolean_handled_accumulator (GSignalInvocationHint * ihint, + GValue * return_accu, const GValue * handler_return, gpointer foobar) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +static void +bacon_video_widget_class_init (BaconVideoWidgetClass * klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + + parent_class = gtk_type_class (gtk_box_get_type ()); + + /* GtkWidget */ + widget_class->size_request = bacon_video_widget_size_request; + widget_class->size_allocate = bacon_video_widget_size_allocate; + widget_class->realize = bacon_video_widget_realize; + widget_class->unrealize = bacon_video_widget_unrealize; + widget_class->show = bacon_video_widget_show; + widget_class->hide = bacon_video_widget_hide; + widget_class->expose_event = bacon_video_widget_expose_event; + widget_class->motion_notify_event = bacon_video_widget_motion_notify; + widget_class->button_press_event = bacon_video_widget_button_press; + widget_class->button_release_event = bacon_video_widget_button_release; + + /* GObject */ + object_class->set_property = bacon_video_widget_set_property; + object_class->get_property = bacon_video_widget_get_property; + object_class->finalize = bacon_video_widget_finalize; + + /* Properties */ + g_object_class_install_property (object_class, PROP_LOGO_MODE, + g_param_spec_boolean ("logo_mode", NULL, + NULL, FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_POSITION, + g_param_spec_int ("position", NULL, NULL, + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_STREAM_LENGTH, + g_param_spec_int64 ("stream_length", NULL, + NULL, 0, G_MAXINT64, 0, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_PLAYING, + g_param_spec_boolean ("playing", NULL, + NULL, FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_SEEKABLE, + g_param_spec_boolean ("seekable", NULL, + NULL, FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_VOLUME, + g_param_spec_int ("volume", NULL, NULL, + 0, 100, 0, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_SHOWCURSOR, + g_param_spec_boolean ("showcursor", NULL, + NULL, FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_MEDIADEV, + g_param_spec_string ("mediadev", NULL, + NULL, FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_SHOW_VISUALS, + g_param_spec_boolean ("showvisuals", NULL, + NULL, FALSE, + G_PARAM_WRITABLE)); + + /* Signals */ + bvw_signals[SIGNAL_ERROR] = + g_signal_new ("error", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, error), + NULL, NULL, + baconvideowidget_marshal_VOID__STRING_BOOLEAN_BOOLEAN, + G_TYPE_NONE, 3, G_TYPE_STRING, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); + + bvw_signals[SIGNAL_EOS] = + g_signal_new ("eos", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, eos), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + bvw_signals[SIGNAL_GOT_METADATA] = + g_signal_new ("got-metadata", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, got_metadata), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + bvw_signals[SIGNAL_REDIRECT] = + g_signal_new ("got-redirect", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, got_redirect), + NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + bvw_signals[SIGNAL_TITLE_CHANGE] = + g_signal_new ("title-change", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, title_change), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + bvw_signals[SIGNAL_CHANNELS_CHANGE] = + g_signal_new ("channels-change", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, channels_change), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + bvw_signals[SIGNAL_TICK] = + g_signal_new ("tick", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, tick), + NULL, NULL, + baconvideowidget_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN, + G_TYPE_NONE, 4, G_TYPE_INT64, G_TYPE_INT64, G_TYPE_FLOAT, + G_TYPE_BOOLEAN); + + bvw_signals[SIGNAL_BUFFERING] = + g_signal_new ("buffering", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, buffering), + NULL, NULL, + g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); + + /* missing plugins signal: + * - string array: details of missing plugins for libgimme-codec + * - string array: details of missing plugins (human-readable strings) + * - bool: if we managed to start playing something even without those plugins + * return value: callback must return TRUE to indicate that it took some + * action, FALSE will be interpreted as no action taken + */ + bvw_signals[SIGNAL_MISSING_PLUGINS] = + g_signal_new ("missing-plugins", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, /* signal is enough, we don't need a vfunc */ + bvw_boolean_handled_accumulator, NULL, + baconvideowidget_marshal_BOOLEAN__BOXED_BOXED_BOOLEAN, + G_TYPE_BOOLEAN, 3, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_BOOLEAN); +} + +static void +bacon_video_widget_init (BaconVideoWidget * bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (bvw), GTK_CAN_FOCUS); + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (bvw), GTK_NO_WINDOW); + GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (bvw), GTK_DOUBLE_BUFFERED); + + bvw->priv = g_new0 (BaconVideoWidgetPrivate, 1); + bvw->com = g_new0 (BaconVideoWidgetCommon, 1); + + bvw->priv->update_id = 0; + bvw->priv->tagcache = NULL; + bvw->priv->audiotags = NULL; + bvw->priv->videotags = NULL; + + bvw->priv->lock = g_mutex_new (); + + bvw->priv->missing_plugins = NULL; + bvw->priv->plugin_install_in_progress = FALSE; +} + +static void +shrink_toplevel (BaconVideoWidget * bvw) +{ + GtkWidget *toplevel, *widget; + widget = GTK_WIDGET (bvw); + toplevel = gtk_widget_get_toplevel (widget); + if (toplevel != widget && GTK_IS_WINDOW (toplevel) != FALSE) + gtk_window_resize (GTK_WINDOW (toplevel), 1, 1); +} + +static gboolean bvw_query_timeout (BaconVideoWidget *bvw); +static void parse_stream_info (BaconVideoWidget *bvw); + +static void +bvw_update_stream_info (BaconVideoWidget *bvw) +{ + parse_stream_info (bvw); + + /* if we're not interactive, we want to announce metadata + * only later when we can be sure we got it all */ + if (bvw->priv->use_type == BVW_USE_TYPE_VIDEO || + bvw->priv->use_type == BVW_USE_TYPE_AUDIO) { + g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL); + g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0); + } +} + +static void +bvw_handle_application_message (BaconVideoWidget *bvw, GstMessage *msg) +{ + const gchar *msg_name; + + msg_name = gst_structure_get_name (msg->structure); + g_return_if_fail (msg_name != NULL); + + GST_DEBUG ("Handling application message: %" GST_PTR_FORMAT, msg->structure); + + if (strcmp (msg_name, "notify-streaminfo") == 0) { + bvw_update_stream_info (bvw); + } + else if (strcmp (msg_name, "video-size") == 0) { + /* if we're not interactive, we want to announce metadata + * only later when we can be sure we got it all */ + if (bvw->priv->use_type == BVW_USE_TYPE_VIDEO || + bvw->priv->use_type == BVW_USE_TYPE_AUDIO) { + g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL); + } + + if (bvw->priv->auto_resize && !bvw->priv->fullscreen_mode) { + gint w, h; + + shrink_toplevel (bvw); + get_media_size (bvw, &w, &h); + totem_widget_set_preferred_size (GTK_WIDGET (bvw), w, h); + } else { + bacon_video_widget_size_allocate (GTK_WIDGET (bvw), + >K_WIDGET (bvw)->allocation); + + /* Uhm, so this ugly hack here makes media loading work for + * weird laptops with NVIDIA graphics cards... Dunno what the + * bug is really, but hey, it works. :). */ + if (GTK_WIDGET (bvw)->window) { + gdk_window_hide (GTK_WIDGET (bvw)->window); + gdk_window_show (GTK_WIDGET (bvw)->window); + + bacon_video_widget_expose_event (GTK_WIDGET (bvw), NULL); + } + } + } else { + g_message ("Unhandled application message %s", msg_name); + } +} + +static void +bvw_handle_element_message (BaconVideoWidget *bvw, GstMessage *msg) +{ + const gchar *type_name = NULL; + gchar *src_name; + + src_name = gst_object_get_name (msg->src); + if (msg->structure) + type_name = gst_structure_get_name (msg->structure); + + GST_DEBUG ("from %s: %" GST_PTR_FORMAT, src_name, msg->structure); + + if (type_name == NULL) + goto unhandled; + + if (strcmp (type_name, "redirect") == 0) { + const gchar *new_location; + + new_location = gst_structure_get_string (msg->structure, "new-location"); + GST_DEBUG ("Got redirect to '%s'", GST_STR_NULL (new_location)); + + if (new_location && *new_location) { + g_signal_emit (bvw, bvw_signals[SIGNAL_REDIRECT], 0, new_location); + goto done; + } + } else if (strcmp (type_name, "progress") == 0) { + /* this is similar to buffering messages, but shouldn't affect pipeline + * state; qtdemux emits those when headers are after movie data and + * it is in streaming mode and has to receive all the movie data first */ + if (!bvw->priv->buffering) { + gint percent = 0; + + if (gst_structure_get_int (msg->structure, "percent", &percent)) + g_signal_emit (bvw, bvw_signals[SIGNAL_BUFFERING], 0, percent); + } + goto done; + } else if (strcmp (type_name, "prepare-xwindow-id") == 0 || + strcmp (type_name, "have-xwindow-id") == 0) { + /* we handle these synchroneously or want to ignore them */ + goto done; + } else if (gst_is_missing_plugin_message (msg)) { + bvw->priv->missing_plugins = + g_list_prepend (bvw->priv->missing_plugins, gst_message_ref (msg)); + goto done; + } + +unhandled: + GST_WARNING ("Unhandled element message %s from %s: %" GST_PTR_FORMAT, + GST_STR_NULL (type_name), GST_STR_NULL (src_name), msg); + +done: + g_free (src_name); +} + +/* This is a hack to avoid doing poll_for_state_change() indirectly + * from the bus message callback (via EOS => totem => close => wait for READY) + * and deadlocking there. We need something like a + * gst_bus_set_auto_flushing(bus, FALSE) ... */ +static gboolean +bvw_signal_eos_delayed (gpointer user_data) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (user_data); + + g_signal_emit (bvw, bvw_signals[SIGNAL_EOS], 0, NULL); + bvw->priv->eos_id = 0; + return FALSE; +} + +static void +bvw_reconfigure_tick_timeout (BaconVideoWidget *bvw, guint msecs) +{ + if (bvw->priv->update_id != 0) { + GST_DEBUG ("removing tick timeout"); + g_source_remove (bvw->priv->update_id); + bvw->priv->update_id = 0; + } + if (msecs > 0) { + GST_DEBUG ("adding tick timeout (at %ums)", msecs); + bvw->priv->update_id = + g_timeout_add (msecs, (GSourceFunc) bvw_query_timeout, bvw); + } +} + +/* returns TRUE if the error/signal has been handled and should be ignored */ +static gboolean +bvw_emit_missing_plugins_signal (BaconVideoWidget * bvw, gboolean prerolled) +{ + gboolean handled = FALSE; + gchar **descriptions, **details; + + details = bvw_get_missing_plugins_details (bvw->priv->missing_plugins); + descriptions = bvw_get_missing_plugins_descriptions (bvw->priv->missing_plugins); + + GST_LOG ("emitting missing-plugins signal (prerolled=%d)", prerolled); + + g_signal_emit (bvw, bvw_signals[SIGNAL_MISSING_PLUGINS], 0, + details, descriptions, prerolled, &handled); + GST_DEBUG ("missing-plugins signal was %shandled", (handled) ? "" : "not "); + + g_strfreev (descriptions); + g_strfreev (details); + + if (handled) { + bvw->priv->plugin_install_in_progress = TRUE; + bvw_clear_missing_plugins_messages (bvw); + } + + /* if it wasn't handled, we might need the list of missing messages again + * later to create a proper error message with details of what's missing */ + + return handled; +} + +/* returns TRUE if the error has been handled and should be ignored */ +static gboolean +bvw_check_missing_plugins_error (BaconVideoWidget * bvw, GstMessage * err_msg) +{ + gboolean ret = FALSE; + GError *err = NULL; + + if (bvw->priv->missing_plugins == NULL) { + GST_DEBUG ("no missing-plugin messages"); + return FALSE; + } + + gst_message_parse_error (err_msg, &err, NULL); + + if (!is_error (err, CORE, MISSING_PLUGIN) && + !is_error (err, STREAM, CODEC_NOT_FOUND)) { + GST_DEBUG ("neither CORE/MISSING_PLUGIN nor STREAM/CODEC_NOT_FOUND error"); + } else { + ret = bvw_emit_missing_plugins_signal (bvw, FALSE); + if (ret) { + /* If it was handled, stop playback to make sure we're not processing any + * other error messages that might also be on the bus */ + bacon_video_widget_stop (bvw); + } + } + + g_error_free (err); + return ret; +} + +/* returns TRUE if the error/signal has been handled and should be ignored */ +static gboolean +bvw_check_missing_plugins_on_preroll (BaconVideoWidget * bvw) +{ + if (bvw->priv->missing_plugins == NULL) { + GST_DEBUG ("no missing-plugin messages"); + return FALSE; + } + + return bvw_emit_missing_plugins_signal (bvw, TRUE); +} + +static void +bvw_bus_message_cb (GstBus * bus, GstMessage * message, gpointer data) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) data; + GstMessageType msg_type; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + msg_type = GST_MESSAGE_TYPE (message); + + /* somebody else is handling the message, probably in poll_for_state_change */ + if (bvw->priv->ignore_messages_mask & msg_type) { + GST_LOG ("Ignoring %s message from element %" GST_PTR_FORMAT + " as requested: %" GST_PTR_FORMAT, GST_MESSAGE_TYPE_NAME (message), + message->src, message); + return; + } + + if (msg_type != GST_MESSAGE_STATE_CHANGED) { + gchar *src_name = gst_object_get_name (message->src); + GST_LOG ("Handling %s message from element %s", + gst_message_type_get_name (msg_type), src_name); + g_free (src_name); + } + + switch (msg_type) { + case GST_MESSAGE_ERROR: { + bvw_error_msg_print_dbg (message); + + if (!bvw_check_missing_plugins_error (bvw, message)) { + GError *error; + + error = bvw_error_from_gst_error (bvw, message); + + g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0, + error->message, TRUE, FALSE); + + if (bvw->priv->play) + gst_element_set_state (bvw->priv->play, GST_STATE_NULL); + + bvw->priv->target_state = GST_STATE_NULL; + bvw->priv->buffering = FALSE; + g_error_free (error); + } + break; + } + case GST_MESSAGE_WARNING: { + GST_WARNING ("Warning message: %" GST_PTR_FORMAT, message); + break; + } + case GST_MESSAGE_TAG: { + GstTagList *tag_list, *result; + GstElementFactory *f; + + gst_message_parse_tag (message, &tag_list); + + GST_DEBUG ("Tags: %" GST_PTR_FORMAT, tag_list); + + /* all tags */ + result = gst_tag_list_merge (bvw->priv->tagcache, tag_list, + GST_TAG_MERGE_KEEP); + if (bvw->priv->tagcache) + gst_tag_list_free (bvw->priv->tagcache); + bvw->priv->tagcache = result; + + /* media-type-specific tags */ + if (GST_IS_ELEMENT (message->src) && + (f = gst_element_get_factory (GST_ELEMENT (message->src)))) { + const gchar *klass = gst_element_factory_get_klass (f); + GstTagList **cache = NULL; + + if (g_strrstr (klass, "Video")) { + cache = &bvw->priv->videotags; + } else if (g_strrstr (klass, "Audio")) { + cache = &bvw->priv->audiotags; + } + + if (cache) { + result = gst_tag_list_merge (*cache, tag_list, GST_TAG_MERGE_KEEP); + if (*cache) + gst_tag_list_free (*cache); + *cache = result; + } + } + + /* clean up */ + gst_tag_list_free (tag_list); + + /* if we're not interactive, we want to announce metadata + * only later when we can be sure we got it all */ + if (bvw->priv->use_type == BVW_USE_TYPE_VIDEO || + bvw->priv->use_type == BVW_USE_TYPE_AUDIO) { + g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0); + } + break; + } + case GST_MESSAGE_EOS: + GST_DEBUG ("EOS message"); + /* update slider one last time */ + bvw_query_timeout (bvw); + if (bvw->priv->eos_id == 0) + bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw); + break; + case GST_MESSAGE_BUFFERING: { + gint percent = 0; + + /* FIXME: use gst_message_parse_buffering() once core 0.10.11 is out */ + gst_structure_get_int (message->structure, "buffer-percent", &percent); + g_signal_emit (bvw, bvw_signals[SIGNAL_BUFFERING], 0, percent); + + if (percent >= 100) { + /* a 100% message means buffering is done */ + bvw->priv->buffering = FALSE; + /* if the desired state is playing, go back */ + if (bvw->priv->target_state == GST_STATE_PLAYING) { + GST_DEBUG ("Buffering done, setting pipeline back to PLAYING"); + gst_element_set_state (bvw->priv->play, GST_STATE_PLAYING); + } else { + GST_DEBUG ("Buffering done, keeping pipeline PAUSED"); + } + } else if (bvw->priv->buffering == FALSE && + bvw->priv->target_state == GST_STATE_PLAYING) { + GstState cur_state; + + gst_element_get_state (bvw->priv->play, &cur_state, NULL, 0); + if (cur_state == GST_STATE_PLAYING) { + GST_DEBUG ("Buffering ... temporarily pausing playback"); + gst_element_set_state (bvw->priv->play, GST_STATE_PAUSED); + } else { + GST_DEBUG ("Buffering ... prerolling, not doing anything"); + } + bvw->priv->buffering = TRUE; + } else { + GST_LOG ("Buffering ... %d", percent); + } + break; + } + case GST_MESSAGE_APPLICATION: { + bvw_handle_application_message (bvw, message); + break; + } + case GST_MESSAGE_STATE_CHANGED: { + GstState old_state, new_state; + gchar *src_name; + + gst_message_parse_state_changed (message, &old_state, &new_state, NULL); + + if (old_state == new_state) + break; + + /* we only care about playbin (pipeline) state changes */ + if (GST_MESSAGE_SRC (message) != GST_OBJECT (bvw->priv->play)) + break; + + src_name = gst_object_get_name (message->src); + GST_DEBUG ("%s changed state from %s to %s", src_name, + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + g_free (src_name); + + /* now do stuff */ + if (new_state < GST_STATE_PAUSED) { + bvw_reconfigure_tick_timeout (bvw, 0); + } else if (new_state == GST_STATE_PAUSED) { + /* yes, we need to keep the tick timeout running in PAUSED state + * as well, totem depends on that (use lower frequency though) */ + bvw_reconfigure_tick_timeout (bvw, 500); + } else if (new_state > GST_STATE_PAUSED) { + bvw_reconfigure_tick_timeout (bvw, 200); + } + + if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) { + bvw_update_stream_info (bvw); + if (!bvw_check_missing_plugins_on_preroll (bvw)) { + /* show a non-fatal warning message if we can't decode the video */ + bvw_check_if_video_decoder_is_missing (bvw); + } + } else if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_READY) { + bvw->priv->media_has_video = FALSE; + bvw->priv->media_has_audio = FALSE; + + /* clean metadata cache */ + if (bvw->priv->tagcache) { + gst_tag_list_free (bvw->priv->tagcache); + bvw->priv->tagcache = NULL; + } + if (bvw->priv->audiotags) { + gst_tag_list_free (bvw->priv->audiotags); + bvw->priv->audiotags = NULL; + } + if (bvw->priv->videotags) { + gst_tag_list_free (bvw->priv->videotags); + bvw->priv->videotags = NULL; + } + + bvw->priv->video_width = 0; + bvw->priv->video_height = 0; + } + break; + } + case GST_MESSAGE_ELEMENT:{ + bvw_handle_element_message (bvw, message); + break; + } + + case GST_MESSAGE_DURATION: { + /* force _get_stream_length() to do new duration query */ + bvw->priv->stream_length = 0; + if (bacon_video_widget_get_stream_length (bvw) == 0) { + GST_DEBUG ("Failed to query duration after DURATION message?!"); + } + break; + } + + case GST_MESSAGE_CLOCK_PROVIDE: + case GST_MESSAGE_CLOCK_LOST: + case GST_MESSAGE_NEW_CLOCK: + case GST_MESSAGE_STATE_DIRTY: + break; + + default: + g_message ("Unhandled message of type '%s' (0x%x)", + gst_message_type_get_name (msg_type), msg_type); + break; + } +} + +/* FIXME: how to recognise this in 0.9? */ +#if 0 +static void +group_switch (GstElement *play, BaconVideoWidget *bvw) +{ + GstMessage *msg; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + if (bvw->priv->tagcache) { + gst_tag_list_free (bvw->priv->tagcache); + bvw->priv->tagcache = NULL; + } + if (bvw->priv->audiotags) { + gst_tag_list_free (bvw->priv->audiotags); + bvw->priv->audiotags = NULL; + } + if (bvw->priv->videotags) { + gst_tag_list_free (bvw->priv->videotags); + bvw->priv->videotags = NULL; + } + + msg = gst_message_new_application (GST_OBJECT (bvw->priv->play), + gst_structure_new ("notify-streaminfo", NULL)); + gst_element_post_message (bvw->priv->play, msg); +} +#endif + +static void +got_video_size (BaconVideoWidget * bvw) +{ + GstMessage *msg; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + msg = gst_message_new_application (GST_OBJECT (bvw->priv->play), + gst_structure_new ("video-size", "width", G_TYPE_INT, + bvw->priv->video_width, "height", G_TYPE_INT, + bvw->priv->video_height, NULL)); + gst_element_post_message (bvw->priv->play, msg); +} + +static void +got_time_tick (GstElement * play, gint64 time_nanos, BaconVideoWidget * bvw) +{ + gboolean seekable; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + if (bvw->priv->logo_mode != FALSE) + return; + + bvw->priv->current_time_nanos = time_nanos; + + bvw->priv->current_time = (gint64) time_nanos / GST_MSECOND; + + if (bvw->priv->stream_length == 0) { + bvw->priv->current_position = 0; + } else { + bvw->priv->current_position = + (gfloat) bvw->priv->current_time / bvw->priv->stream_length; + } + + if (bvw->priv->stream_length == 0) { + seekable = bacon_video_widget_is_seekable (bvw); + } else { + seekable = TRUE; + } + +/* + GST_DEBUG ("%" GST_TIME_FORMAT ",%" GST_TIME_FORMAT " %s", + GST_TIME_ARGS (bvw->priv->current_time), + GST_TIME_ARGS (bvw->priv->stream_length), + (seekable) ? "TRUE" : "FALSE"); +*/ + + g_signal_emit (bvw, bvw_signals[SIGNAL_TICK], 0, + bvw->priv->current_time, bvw->priv->stream_length, + bvw->priv->current_position, + seekable); +} + +static void +playbin_source_notify_cb (GObject *play, GParamSpec *p, BaconVideoWidget *bvw) +{ + GObject *source = NULL; + + /* CHECKME: do we really need these taglist frees here (tpm)? */ + if (bvw->priv->tagcache) { + gst_tag_list_free (bvw->priv->tagcache); + bvw->priv->tagcache = NULL; + } + if (bvw->priv->audiotags) { + gst_tag_list_free (bvw->priv->audiotags); + bvw->priv->audiotags = NULL; + } + if (bvw->priv->videotags) { + gst_tag_list_free (bvw->priv->videotags); + bvw->priv->videotags = NULL; + } + + g_object_get (play, "source", &source, NULL); + if (!source) + return; + + GST_DEBUG ("Got source of type %s", G_OBJECT_TYPE_NAME (source)); + + if (bvw->priv->media_device) { + if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "device")) { + GST_DEBUG ("Setting device to '%s'", bvw->priv->media_device); + g_object_set (source, "device", bvw->priv->media_device, NULL); + } + } + + g_object_unref (source); +} + +static gboolean +bvw_query_timeout (BaconVideoWidget *bvw) +{ + GstFormat fmt = GST_FORMAT_TIME; + gint64 prev_len = -1; + gint64 pos = -1, len = -1; + + /* check length/pos of stream */ + prev_len = bvw->priv->stream_length; + if (gst_element_query_duration (bvw->priv->play, &fmt, &len)) { + if (len != -1 && fmt == GST_FORMAT_TIME) { + bvw->priv->stream_length = len / GST_MSECOND; + if (bvw->priv->stream_length != prev_len) { + g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL); + } + } + } else { + GST_DEBUG ("could not get duration"); + } + + if (gst_element_query_position (bvw->priv->play, &fmt, &pos)) { + if (pos != -1 && fmt == GST_FORMAT_TIME) { + got_time_tick (GST_ELEMENT (bvw->priv->play), pos, bvw); + } + } else { + GST_DEBUG ("could not get position"); + } + + return TRUE; +} + +static void +caps_set (GObject * obj, + GParamSpec * pspec, BaconVideoWidget * bvw) +{ + GstPad *pad = GST_PAD (obj); + GstStructure *s; + GstCaps *caps; + + if (!(caps = gst_pad_get_negotiated_caps (pad))) + return; + + /* Get video decoder caps */ + s = gst_caps_get_structure (caps, 0); + if (s) { + /* We need at least width/height and framerate */ + if (!(gst_structure_get_fraction (s, "framerate", &bvw->priv->video_fps_n, + &bvw->priv->video_fps_d) && + gst_structure_get_int (s, "width", &bvw->priv->video_width) && + gst_structure_get_int (s, "height", &bvw->priv->video_height))) + return; + + /* Get the movie PAR if available */ + bvw->priv->movie_par = gst_structure_get_value (s, "pixel-aspect-ratio"); + + /* Now set for real */ + bacon_video_widget_set_aspect_ratio (bvw, bvw->priv->ratio_type); + } + + gst_caps_unref (caps); +} + +static void get_visualization_size (BaconVideoWidget *bvw, + int *w, int *h, gint *fps_n, gint *fps_d); + +static void +parse_stream_info (BaconVideoWidget *bvw) +{ + GList *streaminfo = NULL; + GstPad *videopad = NULL; + + g_object_get (bvw->priv->play, "stream-info", &streaminfo, NULL); + streaminfo = g_list_copy (streaminfo); + g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL); + for ( ; streaminfo != NULL; streaminfo = streaminfo->next) { + GObject *info = streaminfo->data; + gint type; + GParamSpec *pspec; + GEnumValue *val; + + if (!info) + continue; + g_object_get (info, "type", &type, NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type"); + val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type); + + if (!g_ascii_strcasecmp (val->value_nick, "audio")) { + bvw->priv->media_has_audio = TRUE; + if (!bvw->priv->media_has_video && bvw->priv->video_window) { + if (bvw->priv->show_vfx) { + gdk_window_show (bvw->priv->video_window); + } else { + gdk_window_hide (bvw->priv->video_window); + } + } + } else if (!g_ascii_strcasecmp (val->value_nick, "video")) { + bvw->priv->media_has_video = TRUE; + if (bvw->priv->video_window) + gdk_window_show (bvw->priv->video_window); + if (!videopad) { + g_object_get (info, "object", &videopad, NULL); + } + } + } + + if (videopad) { + GstCaps *caps; + + if ((caps = gst_pad_get_negotiated_caps (videopad))) { + caps_set (G_OBJECT (videopad), NULL, bvw); + gst_caps_unref (caps); + } + g_signal_connect (videopad, "notify::caps", + G_CALLBACK (caps_set), bvw); + /* FIXME: don't we need to unref the video pad? (tpm) */ + } else if (bvw->priv->show_vfx) { + get_visualization_size (bvw, &bvw->priv->video_width, + &bvw->priv->video_height, NULL, NULL); + } + + g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL); + g_list_free (streaminfo); +} + +static void +playbin_stream_info_notify_cb (GObject * obj, GParamSpec * pspec, gpointer data) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data); + GstMessage *msg; + + /* we're being called from the streaming thread, so don't do anything here */ + GST_LOG ("stream info changed"); + msg = gst_message_new_application (GST_OBJECT (bvw->priv->play), + gst_structure_new ("notify-streaminfo", NULL)); + gst_element_post_message (bvw->priv->play, msg); +} + +static void +bacon_video_widget_finalize (GObject * object) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) object; + + GST_DEBUG ("finalizing"); + + if (bvw->priv->bus) { + /* make bus drop all messages to make sure none of our callbacks is ever + * called again (main loop might be run again to display error dialog) */ + gst_bus_set_flushing (bvw->priv->bus, TRUE); + + if (bvw->priv->sig_bus_sync) + g_signal_handler_disconnect (bvw->priv->bus, bvw->priv->sig_bus_sync); + + if (bvw->priv->sig_bus_async) + g_signal_handler_disconnect (bvw->priv->bus, bvw->priv->sig_bus_async); + + gst_object_unref (bvw->priv->bus); + bvw->priv->bus = NULL; + } + + g_free (bvw->priv->media_device); + bvw->priv->media_device = NULL; + + g_free (bvw->com->mrl); + bvw->com->mrl = NULL; + + if (bvw->priv->vis_element_name) { + g_free (bvw->priv->vis_element_name); + bvw->priv->vis_element_name = NULL; + } + + if (bvw->priv->vis_plugins_list) { + g_list_free (bvw->priv->vis_plugins_list); + bvw->priv->vis_plugins_list = NULL; + } + + if (bvw->priv->play != NULL && GST_IS_ELEMENT (bvw->priv->play)) { + gst_element_set_state (bvw->priv->play, GST_STATE_NULL); + gst_object_unref (bvw->priv->play); + bvw->priv->play = NULL; + } + + if (bvw->priv->update_id) { + g_source_remove (bvw->priv->update_id); + bvw->priv->update_id = 0; + } + + if (bvw->priv->tagcache) { + gst_tag_list_free (bvw->priv->tagcache); + bvw->priv->tagcache = NULL; + } + if (bvw->priv->audiotags) { + gst_tag_list_free (bvw->priv->audiotags); + bvw->priv->audiotags = NULL; + } + if (bvw->priv->videotags) { + gst_tag_list_free (bvw->priv->videotags); + bvw->priv->videotags = NULL; + } + + if (bvw->priv->eos_id != 0) + g_source_remove (bvw->priv->eos_id); + + g_mutex_free (bvw->priv->lock); + + g_free (bvw->priv); + g_free (bvw->com); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +bacon_video_widget_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + BaconVideoWidget *bvw; + + bvw = BACON_VIDEO_WIDGET (object); + + switch (property_id) { + case PROP_LOGO_MODE: + bacon_video_widget_set_logo_mode (bvw, + g_value_get_boolean (value)); + break; + case PROP_SHOWCURSOR: + bacon_video_widget_set_show_cursor (bvw, + g_value_get_boolean (value)); + break; + case PROP_MEDIADEV: + bacon_video_widget_set_media_device (bvw, + g_value_get_string (value)); + break; + case PROP_SHOW_VISUALS: + bacon_video_widget_set_show_visuals (bvw, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +bacon_video_widget_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + BaconVideoWidget *bvw; + + bvw = BACON_VIDEO_WIDGET (object); + + switch (property_id) { + case PROP_LOGO_MODE: + g_value_set_boolean (value, + bacon_video_widget_get_logo_mode (bvw)); + break; + case PROP_POSITION: + g_value_set_int64 (value, bacon_video_widget_get_position (bvw)); + break; + case PROP_STREAM_LENGTH: + g_value_set_int64 (value, + bacon_video_widget_get_stream_length (bvw)); + break; + case PROP_PLAYING: + g_value_set_boolean (value, + bacon_video_widget_is_playing (bvw)); + break; + case PROP_SEEKABLE: + g_value_set_boolean (value, + bacon_video_widget_is_seekable (bvw)); + break; + case PROP_SHOWCURSOR: + g_value_set_boolean (value, + bacon_video_widget_get_show_cursor (bvw)); + break; + case PROP_MEDIADEV: + g_value_set_string (value, bvw->priv->media_device); + break; + case PROP_VOLUME: + g_value_set_int (value, bacon_video_widget_get_volume (bvw)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/* ============================================================= */ +/* */ +/* Public Methods */ +/* */ +/* ============================================================= */ + +char * +bacon_video_widget_get_backend_name (BaconVideoWidget * bvw) +{ + return gst_version_string (); +} + +static gboolean +has_subp (BaconVideoWidget * bvw) +{ + GList *streaminfo = NULL; + gboolean res = FALSE; + + if (bvw->priv->play == NULL || bvw->com->mrl == NULL) + return FALSE; + + g_object_get (G_OBJECT (bvw->priv->play), "stream-info", &streaminfo, NULL); + streaminfo = g_list_copy (streaminfo); + g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL); + for ( ; streaminfo != NULL; streaminfo = streaminfo->next) { + GObject *info = streaminfo->data; + gint type; + GParamSpec *pspec; + GEnumValue *val; + + if (!info) + continue; + g_object_get (info, "type", &type, NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type"); + val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type); + + if (strstr (val->value_name, "SUBPICTURE")) { + res = TRUE; + break; + } + } + g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL); + g_list_free (streaminfo); + + return res; +} + +int +bacon_video_widget_get_subtitle (BaconVideoWidget * bvw) +{ + int subtitle = -1; + + g_return_val_if_fail (bvw != NULL, -2); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -2); + g_return_val_if_fail (bvw->priv->play != NULL, -2); + + if (has_subp (bvw)) + g_object_get (G_OBJECT (bvw->priv->play), "current-subpicture", &subtitle, NULL); + else + g_object_get (G_OBJECT (bvw->priv->play), "current-text", &subtitle, NULL); + + if (subtitle == -1) + subtitle = -2; + + return subtitle; +} + +void +bacon_video_widget_set_subtitle (BaconVideoWidget * bvw, int subtitle) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->play != NULL); + + if (subtitle == -1) + subtitle = 0; + else if (subtitle == -2) + subtitle = -1; + + if (has_subp (bvw)) + g_object_set (bvw->priv->play, "current-subpicture", subtitle, NULL); + else + g_object_set (bvw->priv->play, "current-text", subtitle, NULL); +} + +gboolean +bacon_video_widget_has_next_track (BaconVideoWidget *bvw) +{ + //FIXME + return TRUE; +} + +gboolean +bacon_video_widget_has_previous_track (BaconVideoWidget *bvw) +{ + //FIXME + return TRUE; +} + +static GList * +get_stream_info_objects_for_type (BaconVideoWidget * bvw, const gchar * typestr) +{ + GList *streaminfo = NULL, *ret = NULL; + + if (bvw->priv->play == NULL || bvw->com->mrl == NULL) + return NULL; + + g_object_get (G_OBJECT (bvw->priv->play), "stream-info", &streaminfo, NULL); + streaminfo = g_list_copy (streaminfo); + g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL); + for ( ; streaminfo != NULL; streaminfo = streaminfo->next) { + GObject *info; + + info = streaminfo->data; + if (info) { + GParamSpec *pspec; + GEnumValue *val; + gint type; + + g_object_get (info, "type", &type, NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type"); + val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type); + if (val && g_ascii_strcasecmp (val->value_nick, typestr) == 0) { + ret = g_list_prepend (ret, g_object_ref (info)); + } + } + } + g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL); + g_list_free (streaminfo); + return g_list_reverse (ret); +} + +static GList * +get_list_of_type (BaconVideoWidget * bvw, const gchar * type_name) +{ + GList *streaminfo = NULL, *ret = NULL; + gint num = 0; + + if (bvw->priv->play == NULL || bvw->com->mrl == NULL) + return NULL; + + g_object_get (G_OBJECT (bvw->priv->play), "stream-info", &streaminfo, NULL); + streaminfo = g_list_copy (streaminfo); + g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL); + for ( ; streaminfo != NULL; streaminfo = streaminfo->next) { + GObject *info = streaminfo->data; + gint type; + GParamSpec *pspec; + GEnumValue *val; + gchar *lc = NULL, *cd = NULL; + + if (!info) + continue; + g_object_get (info, "type", &type, NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type"); + val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type); + if (g_ascii_strcasecmp (val->value_nick, type_name) == 0) { + g_object_get (info, "codec", &cd, "language-code", &lc, NULL); + + if (lc) { + ret = g_list_prepend (ret, lc); + g_free (cd); + } else if (cd) { + ret = g_list_prepend (ret, cd); + } else { + ret = g_list_prepend (ret, g_strdup_printf ("%s %d", type_name, num++)); + } + } + } + g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL); + g_list_free (streaminfo); + + return g_list_reverse (ret); +} + +GList * bacon_video_widget_get_subtitles (BaconVideoWidget * bvw) +{ + GList *list; + + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (bvw->priv->play != NULL, NULL); + + if (!(list = get_list_of_type (bvw, "SUBPICTURE"))) + list = get_list_of_type (bvw, "TEXT"); + + return list; +} + +GList * bacon_video_widget_get_languages (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (bvw->priv->play != NULL, NULL); + + return get_list_of_type (bvw, "AUDIO"); +} + +int +bacon_video_widget_get_language (BaconVideoWidget * bvw) +{ + int language = -1; + + g_return_val_if_fail (bvw != NULL, -2); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -2); + g_return_val_if_fail (bvw->priv->play != NULL, -2); + + g_object_get (G_OBJECT (bvw->priv->play), "current-audio", &language, NULL); + + if (language == -1) + language = -2; + + return language; +} + +void +bacon_video_widget_set_language (BaconVideoWidget * bvw, int language) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->play != NULL); + + if (language == -1) + language = 0; + else if (language == -2) + language = -1; + + GST_DEBUG ("setting language to %d", language); + + g_object_set (bvw->priv->play, "current-audio", language, NULL); + + g_object_get (bvw->priv->play, "current-audio", &language, NULL); + GST_DEBUG ("current-audio now: %d", language); + + /* so it updates its metadata for the newly-selected stream */ + g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL); + g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0); +} + +static guint +connection_speed_enum_to_kbps (gint speed) +{ + static const guint conv_table[] = { 14400, 19200, 28800, 33600, 34400, 56000, + 112000, 256000, 384000, 512000, 1536000, 10752000 }; + + g_return_val_if_fail (speed >= 0 && (guint) speed < G_N_ELEMENTS (conv_table), 0); + + /* must round up so that the correct streams are chosen and not ignored + * due to rounding errors when doing kbps <=> bps */ + return (conv_table[speed] / 1000) + + (((conv_table[speed] % 1000) != 0) ? 1 : 0); +} + +int +bacon_video_widget_get_connection_speed (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + + return bvw->priv->connection_speed; +} + +void +bacon_video_widget_set_connection_speed (BaconVideoWidget * bvw, int speed) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + if (bvw->priv->connection_speed != speed) { + bvw->priv->connection_speed = speed; + gconf_client_set_int (bvw->priv->gc, + GCONF_PREFIX"/connection_speed", speed, NULL); + } + + if (bvw->priv->play != NULL && + g_object_class_find_property (G_OBJECT_GET_CLASS (bvw->priv->play), "connection-speed")) { + guint kbps = connection_speed_enum_to_kbps (speed); + + GST_LOG ("Setting connection speed %d (= %d kbps)", speed, kbps); + g_object_set (bvw->priv->play, "connection-speed", kbps, NULL); + } +} + +void +bacon_video_widget_set_deinterlacing (BaconVideoWidget * bvw, + gboolean deinterlace) +{ +} + +gboolean +bacon_video_widget_get_deinterlacing (BaconVideoWidget * bvw) +{ + return FALSE; +} + +void +bacon_video_widget_set_tv_out (BaconVideoWidget * bvw, TvOutType tvout) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + bvw->priv->tv_out_type = tvout; + gconf_client_set_int (bvw->priv->gc, + GCONF_PREFIX"/tv_out_type", tvout, NULL); + +#ifdef HAVE_NVTV + if (tvout == TV_OUT_NVTV_PAL) { + nvtv_simple_set_tvsystem(NVTV_SIMPLE_TVSYSTEM_PAL); + } else if (tvout == TV_OUT_NVTV_NTSC) { + nvtv_simple_set_tvsystem(NVTV_SIMPLE_TVSYSTEM_NTSC); + } +#endif +} + +TvOutType +bacon_video_widget_get_tv_out (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + + return bvw->priv->tv_out_type; +} + +static gint +get_num_audio_channels (BaconVideoWidget * bvw) +{ + gint channels; + + switch (bvw->priv->speakersetup) { + case BVW_AUDIO_SOUND_STEREO: + channels = 2; + break; + case BVW_AUDIO_SOUND_4CHANNEL: + channels = 4; + break; + case BVW_AUDIO_SOUND_5CHANNEL: + channels = 5; + break; + case BVW_AUDIO_SOUND_41CHANNEL: + /* so alsa has this as 5.1, but empty center speaker. We don't really + * do that yet. ;-). So we'll take the placebo approach. */ + case BVW_AUDIO_SOUND_51CHANNEL: + channels = 6; + break; + case BVW_AUDIO_SOUND_AC3PASSTHRU: + default: + g_return_val_if_reached (-1); + } + + return channels; +} + +static GstCaps * +fixate_to_num (const GstCaps * in_caps, gint channels) +{ + gint n, count; + GstStructure *s; + const GValue *v; + GstCaps *out_caps; + + out_caps = gst_caps_copy (in_caps); + + count = gst_caps_get_size (out_caps); + for (n = 0; n < count; n++) { + s = gst_caps_get_structure (out_caps, n); + v = gst_structure_get_value (s, "channels"); + if (!v) + continue; + + /* get channel count (or list of ~) */ + gst_structure_fixate_field_nearest_int (s, "channels", channels); + } + + return out_caps; +} + +static void +set_audio_filter (BaconVideoWidget *bvw) +{ + gint channels; + GstCaps *caps, *res; + GstPad *pad; + + /* reset old */ + g_object_set (bvw->priv->audio_capsfilter, "caps", NULL, NULL); + + /* construct possible caps to filter down to our chosen caps */ + /* Start with what the audio sink supports, but limit the allowed + * channel count to our speaker output configuration */ + pad = gst_element_get_pad (bvw->priv->audio_capsfilter, "src"); + caps = gst_pad_peer_get_caps (pad); + gst_object_unref (pad); + + if ((channels = get_num_audio_channels (bvw)) == -1) + return; + + res = fixate_to_num (caps, channels); + gst_caps_unref (caps); + + /* set */ + if (res && gst_caps_is_empty (res)) { + gst_caps_unref (res); + res = NULL; + } + g_object_set (bvw->priv->audio_capsfilter, "caps", res, NULL); + + if (res) { + gst_caps_unref (res); + } + + /* reset */ + pad = gst_element_get_pad (bvw->priv->audio_capsfilter, "src"); + gst_pad_set_caps (pad, NULL); + gst_object_unref (pad); +} + +BaconVideoWidgetAudioOutType +bacon_video_widget_get_audio_out_type (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + + return bvw->priv->speakersetup; +} + +gboolean +bacon_video_widget_set_audio_out_type (BaconVideoWidget *bvw, + BaconVideoWidgetAudioOutType type) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + + if (type == bvw->priv->speakersetup) + return FALSE; + else if (type == BVW_AUDIO_SOUND_AC3PASSTHRU) + return FALSE; + + bvw->priv->speakersetup = type; + gconf_client_set_int (bvw->priv->gc, + GCONF_PREFIX"/audio_output_type", type, NULL); + + set_audio_filter (bvw); + + return FALSE; +} + +/* =========================================== */ +/* */ +/* Play/Pause, Stop */ +/* */ +/* =========================================== */ + +static GError* +bvw_error_from_gst_error (BaconVideoWidget *bvw, GstMessage * err_msg) +{ + const gchar *src_typename; + GError *ret = NULL; + GError *e = NULL; + + GST_LOG ("resolving error message %" GST_PTR_FORMAT, err_msg); + + src_typename = (err_msg->src) ? G_OBJECT_TYPE_NAME (err_msg->src) : NULL; + + gst_message_parse_error (err_msg, &e, NULL); + + if (is_error (e, RESOURCE, NOT_FOUND) || + is_error (e, RESOURCE, OPEN_READ)) { +#if 0 + if (strchr (mrl, ':') && + (g_str_has_prefix (mrl, "dvd") || + g_str_has_prefix (mrl, "cd") || + g_str_has_prefix (mrl, "vcd"))) { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_INVALID_DEVICE, + e->message); + } else { +#endif + if (e->code == GST_RESOURCE_ERROR_NOT_FOUND) { + if (GST_IS_BASE_AUDIO_SINK (err_msg->src)) { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_AUDIO_PLUGIN, + _("The requested audio output was not found. " + "Please select another audio output in the Multimedia " + "Systems Selector.")); + } else { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_NOT_FOUND, + _("Location not found.")); + } + } else { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_PERMISSION, + _("Could not open location; " + "You may not have permission to open the file.")); + } +#if 0 + } +#endif + } else if (is_error (e, RESOURCE, BUSY)) { + if (GST_IS_VIDEO_SINK (err_msg->src)) { + /* a somewhat evil check, but hey.. */ + ret = g_error_new_literal (BVW_ERROR, + BVW_ERROR_VIDEO_PLUGIN, + _("The video output is in use by another application. " + "Please close other video applications, or select " + "another video output in the Multimedia Systems Selector.")); + } else if (GST_IS_BASE_AUDIO_SINK (err_msg->src)) { + ret = g_error_new_literal (BVW_ERROR, + BVW_ERROR_AUDIO_BUSY, + _("The audio output is in use by another application. " + "Please select another audio output in the Multimedia Systems Selector. " + "You may want to consider using a sound server.")); + } + } else if (e->domain == GST_RESOURCE_ERROR) { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_GENERIC, + e->message); + } else if (is_error (e, CORE, MISSING_PLUGIN) || + is_error (e, STREAM, CODEC_NOT_FOUND)) { + if (bvw->priv->missing_plugins != NULL) { + gchar **descs, *msg = NULL; + guint num; + + descs = bvw_get_missing_plugins_descriptions (bvw->priv->missing_plugins); + num = g_list_length (bvw->priv->missing_plugins); + + if (is_error (e, CORE, MISSING_PLUGIN)) { + /* should be exactly one missing thing (source or converter) */ + msg = g_strdup_printf (_("The playback of this movie requires a '%s' " + "plugin which is not installed."), descs[0]); + } else { + gchar *desc_list; + + desc_list = g_strjoinv ("\n", descs); + msg = g_strdup_printf (ngettext (_("The playback of this movie " + "requires a %s plugin which is not installed."), _("The playback " + "of this movie requires the following decoders which are not " + "installed:\n\n%s"), num), (num == 1) ? descs[0] : desc_list); + g_free (desc_list); + } + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_CODEC_NOT_HANDLED, msg); + g_free (msg); + g_strfreev (descs); + } else { + GST_LOG ("no missing plugin messages, posting generic error"); + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_CODEC_NOT_HANDLED, + e->message); + } + } else if (is_error (e, STREAM, WRONG_TYPE) || + is_error (e, STREAM, NOT_IMPLEMENTED)) { + if (src_typename) { + ret = g_error_new (BVW_ERROR, BVW_ERROR_CODEC_NOT_HANDLED, "%s: %s", + src_typename, e->message); + } else { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_CODEC_NOT_HANDLED, + e->message); + } + } else if (is_error (e, STREAM, FAILED) && + src_typename && strncmp (src_typename, "GstTypeFind", 11) == 0) { + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_READ_ERROR, + _("Cannot play this file over the network. " + "Try downloading it to disk first.")); + } else { + /* generic error, no code; take message */ + ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_GENERIC, + e->message); + } + g_error_free (e); + bvw_clear_missing_plugins_messages (bvw); + + return ret; +} + +static gboolean +poll_for_state_change_full (BaconVideoWidget *bvw, GstElement *element, + GstState state, GstMessage ** err_msg, gint64 timeout) +{ + GstBus *bus; + GstMessageType events, saved_events; + + g_assert (err_msg != NULL); + + bus = gst_element_get_bus (element); + + events = GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS; + + saved_events = bvw->priv->ignore_messages_mask; + + if (element != NULL && element == bvw->priv->play) { + /* we do want the main handler to process state changed messages for + * playbin as well, otherwise it won't hook up the timeout etc. */ + bvw->priv->ignore_messages_mask |= (events ^ GST_MESSAGE_STATE_CHANGED); + } else { + bvw->priv->ignore_messages_mask |= events; + } + + while (TRUE) { + GstMessage *message; + GstElement *src; + + message = gst_bus_poll (bus, events, timeout); + + if (!message) + goto timed_out; + + src = (GstElement*)GST_MESSAGE_SRC (message); + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_STATE_CHANGED: { + GstState old, new, pending; + + if (src == element) { + gst_message_parse_state_changed (message, &old, &new, &pending); + if (new == state) { + gst_message_unref (message); + goto success; + } + } + break; + } + case GST_MESSAGE_ERROR: { + bvw_error_msg_print_dbg (message); + *err_msg = message; + message = NULL; + goto error; + break; + } + case GST_MESSAGE_EOS: { + GError *e = NULL; + + gst_message_unref (message); + e = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_GENERIC, + _("Media file could not be played.")); + *err_msg = gst_message_new_error (GST_OBJECT (bvw->priv->play), e, NULL); + g_error_free (e); + goto error; + break; + } + default: + g_assert_not_reached (); + break; + } + + gst_message_unref (message); + } + + g_assert_not_reached (); + +success: + /* state change succeeded */ + GST_DEBUG ("state change to %s succeeded", gst_element_state_get_name (state)); + bvw->priv->ignore_messages_mask = saved_events; + return TRUE; + +timed_out: + /* it's taking a long time to open -- just tell totem it was ok, this allows + * the user to stop the loading process with the normal stop button */ + GST_DEBUG ("state change to %s timed out, returning success and handling " + "errors asynchroneously", gst_element_state_get_name (state)); + bvw->priv->ignore_messages_mask = saved_events; + return TRUE; + +error: + GST_DEBUG ("error while waiting for state change to %s: %" GST_PTR_FORMAT, + gst_element_state_get_name (state), *err_msg); + /* already set *err_msg */ + bvw->priv->ignore_messages_mask = saved_events; + return FALSE; +} + +gboolean +bacon_video_widget_open_with_subtitle (BaconVideoWidget * bvw, + const gchar * mrl, const gchar *subtitle_uri, GError ** error) +{ + GstMessage *err_msg = NULL; + gboolean ret; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (mrl != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->play != NULL, FALSE); + + /* So we aren't closed yet... */ + if (bvw->com->mrl) { + bacon_video_widget_close (bvw); + } + + GST_DEBUG ("mrl = %s", GST_STR_NULL (mrl)); + GST_DEBUG ("subtitle_uri = %s", GST_STR_NULL (subtitle_uri)); + + /* hmm... */ + if (bvw->com->mrl && strcmp (bvw->com->mrl, mrl) == 0) { + GST_DEBUG ("same as current mrl"); + /* FIXME: shouldn't we ensure playing state here? */ + return TRUE; + } + + /* this allows non-URI type of files in the thumbnailer and so on */ + g_free (bvw->com->mrl); + if (mrl[0] == '/') { + bvw->com->mrl = g_strdup_printf ("file://%s", mrl); + } else { + if (strchr (mrl, ':')) { + bvw->com->mrl = g_strdup (mrl); + } else { + gchar *cur_dir = g_get_current_dir (); + + if (!cur_dir) { + g_set_error (error, BVW_ERROR, BVW_ERROR_GENERIC, + _("Failed to retrieve working directory")); + return FALSE; + } + bvw->com->mrl = g_strdup_printf ("file://%s/%s", cur_dir, mrl); + g_free (cur_dir); + } + } + + if (g_str_has_prefix (mrl, "icy:") != FALSE) { + /* Handle "icy://" URLs from QuickTime */ + bvw->com->mrl = g_strdup_printf ("http:%s", mrl + 4); + } else if (g_str_has_prefix (mrl, "dvd:///")) { + /* this allows to play backups of dvds */ + g_free (bvw->com->mrl); + bvw->com->mrl = g_strdup ("dvd://"); + bacon_video_widget_set_media_device (bvw, mrl + strlen ("dvd://")); + } + + bvw->priv->got_redirect = FALSE; + bvw->priv->media_has_video = FALSE; + bvw->priv->media_has_audio = FALSE; + bvw->priv->stream_length = 0; + bvw->priv->ignore_messages_mask = 0; + + /* We hide the video window for now. Will show when video of vfx comes up */ + if (bvw->priv->video_window) { + gdk_window_hide (bvw->priv->video_window); + /* We also take the whole widget until we know video size */ + gdk_window_move_resize (bvw->priv->video_window, 0, 0, + GTK_WIDGET (bvw)->allocation.width, + GTK_WIDGET (bvw)->allocation.height); + } + + /* Visualization settings changed */ + if (bvw->priv->vis_changed) { + setup_vis (bvw); + } + + if (g_strrstr (bvw->com->mrl, "#subtitle:")) { + gchar **uris; + gchar *subtitle_uri; + + uris = g_strsplit (bvw->com->mrl, "#subtitle:", 2); + /* Try to fix subtitle uri if needed */ + if (uris[1][0] == '/') { + subtitle_uri = g_strdup_printf ("file://%s", uris[1]); + } + else { + if (strchr (uris[1], ':')) { + subtitle_uri = g_strdup (uris[1]); + } else { + gchar *cur_dir = g_get_current_dir (); + if (!cur_dir) { + g_set_error (error, BVW_ERROR, BVW_ERROR_GENERIC, + _("Failed to retrieve working directory")); + return FALSE; + } + subtitle_uri = g_strdup_printf ("file://%s/%s", cur_dir, uris[1]); + g_free (cur_dir); + } + } + g_object_set (bvw->priv->play, "uri", bvw->com->mrl, + "suburi", subtitle_uri, NULL); + g_free (subtitle_uri); + g_strfreev (uris); + } else { + g_object_set (bvw->priv->play, "uri", bvw->com->mrl, + "suburi", subtitle_uri, NULL); + } + + bvw->priv->seekable = -1; + bvw->priv->target_state = GST_STATE_PAUSED; + bvw_clear_missing_plugins_messages (bvw); + + gst_element_set_state (bvw->priv->play, GST_STATE_PAUSED); + + if (bvw->priv->use_type == BVW_USE_TYPE_AUDIO || + bvw->priv->use_type == BVW_USE_TYPE_VIDEO) { + GST_DEBUG ("normal playback, handling all errors asynchroneously"); + ret = TRUE; + } else { + /* used as thumbnailer or metadata extractor for properties dialog. In + * this case, wait for any state change to really finish and process any + * pending tag messages, so that the information is available right away */ + GST_DEBUG ("waiting for state changed to PAUSED to complete"); + ret = poll_for_state_change_full (bvw, bvw->priv->play, + GST_STATE_PAUSED, &err_msg, -1); + + bvw_process_pending_tag_messages (bvw); + bacon_video_widget_get_stream_length (bvw); + GST_DEBUG ("stream length = %u", bvw->priv->stream_length); + + /* even in case of an error (e.g. no decoders installed) we might still + * have useful metadata (like codec types, duration, etc.) */ + g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL); + } + + if (ret) { + g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0); + } else { + GST_DEBUG ("Error on open: %" GST_PTR_FORMAT, err_msg); + if (bvw_check_missing_plugins_error (bvw, err_msg)) { + /* totem will try to start playing, so ignore all messages on the bus */ + bvw->priv->ignore_messages_mask |= GST_MESSAGE_ERROR; + GST_LOG ("missing plugins handled, ignoring error and returning TRUE"); + gst_message_unref (err_msg); + err_msg = NULL; + ret = TRUE; + } else { + bvw->priv->ignore_messages_mask |= GST_MESSAGE_ERROR; + bvw_stop_play_pipeline (bvw); + g_free (bvw->com->mrl); + bvw->com->mrl = NULL; + } + } + + /* When opening a new media we want to redraw ourselves */ + gtk_widget_queue_draw (GTK_WIDGET (bvw)); + + if (err_msg != NULL) { + if (error) { + *error = bvw_error_from_gst_error (bvw, err_msg); + } else { + GST_WARNING ("Got error, but caller is not collecting error details!"); + } + gst_message_unref (err_msg); + } + + return ret; +} + +gboolean +bacon_video_widget_play (BaconVideoWidget * bvw, GError ** error) +{ + GstState cur_state; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + g_return_val_if_fail (bvw->com->mrl != NULL, FALSE); + + bvw->priv->target_state = GST_STATE_PLAYING; + + /* no need to actually go into PLAYING in capture/metadata mode (esp. + * not with sinks that don't sync to the clock), we'll get everything + * we need by prerolling the pipeline, and that is done in _open() */ + if (bvw->priv->use_type == BVW_USE_TYPE_CAPTURE || + bvw->priv->use_type == BVW_USE_TYPE_METADATA) { + return TRUE; + } + + /* just lie and do nothing in this case */ + gst_element_get_state (bvw->priv->play, &cur_state, NULL, 0); + if (bvw->priv->plugin_install_in_progress && cur_state != GST_STATE_PAUSED) { + GST_DEBUG ("plugin install in progress and nothing to play, doing nothing"); + return TRUE; + } + + GST_DEBUG ("play"); + gst_element_set_state (bvw->priv->play, GST_STATE_PLAYING); + + /* will handle all errors asynchroneously */ + return TRUE; +} + +gboolean +bacon_video_widget_can_direct_seek (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + return bacon_video_widget_common_can_direct_seek (bvw->com); +} + +gboolean +bacon_video_widget_seek_time (BaconVideoWidget *bvw, gint64 time, GError **gerror) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + GST_LOG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (time * GST_MSECOND)); + + if (time > bvw->priv->stream_length + && bvw->priv->stream_length > 0 + && !g_str_has_prefix (bvw->com->mrl, "dvd:") + && !g_str_has_prefix (bvw->com->mrl, "vcd:")) { + if (bvw->priv->eos_id == 0) + bvw->priv->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw); + return TRUE; + } + + /* Emit a time tick of where we are going, we are paused */ + got_time_tick (bvw->priv->play, time * GST_MSECOND, bvw); + + gst_element_seek (bvw->priv->play, 1.0, + GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, + GST_SEEK_TYPE_SET, time * GST_MSECOND, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + + gst_element_get_state (bvw->priv->play, NULL, NULL, 100 * GST_MSECOND); + + return TRUE; +} + +gboolean +bacon_video_widget_seek (BaconVideoWidget *bvw, float position, GError **error) +{ + gint64 seek_time, length_nanos; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + length_nanos = (gint64) (bvw->priv->stream_length * GST_MSECOND); + seek_time = (gint64) (length_nanos * position); + + GST_LOG ("Seeking to %3.2f%% %" GST_TIME_FORMAT, position, + GST_TIME_ARGS (seek_time)); + + return bacon_video_widget_seek_time (bvw, seek_time / GST_MSECOND, error); +} + +static void +bvw_stop_play_pipeline (BaconVideoWidget * bvw) +{ + GstState cur_state; + + gst_element_get_state (bvw->priv->play, &cur_state, NULL, 0); + if (cur_state > GST_STATE_READY) { + GstMessage *msg; + GstBus *bus; + + GST_DEBUG ("stopping"); + gst_element_set_state (bvw->priv->play, GST_STATE_READY); + + /* process all remaining state-change messages so everything gets + * cleaned up properly (before the state change to NULL flushes them) */ + GST_DEBUG ("processing pending state-change messages"); + bus = gst_element_get_bus (bvw->priv->play); + while ((msg = gst_bus_poll (bus, GST_MESSAGE_STATE_CHANGED, 0))) { + gst_bus_async_signal_func (bus, msg, NULL); + gst_message_unref (msg); + } + gst_object_unref (bus); + } + + gst_element_set_state (bvw->priv->play, GST_STATE_NULL); + bvw->priv->target_state = GST_STATE_NULL; + bvw->priv->buffering = FALSE; + bvw->priv->plugin_install_in_progress = FALSE; + bvw->priv->ignore_messages_mask = 0; + GST_DEBUG ("stopped"); +} + +void +bacon_video_widget_stop (BaconVideoWidget * bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + GST_LOG ("Stopping"); + bvw_stop_play_pipeline (bvw); + + /* Reset position to 0 when stopping */ + got_time_tick (GST_ELEMENT (bvw->priv->play), 0, bvw); +} + +void +bacon_video_widget_close (BaconVideoWidget * bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + GST_LOG ("Closing"); + bvw_stop_play_pipeline (bvw); + + if (bvw->com->mrl) { + g_free (bvw->com->mrl); + bvw->com->mrl = NULL; + } + + g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0); +} + +void +bacon_video_widget_dvd_event (BaconVideoWidget * bvw, + BaconVideoWidgetDVDEvent type) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + switch (type) { + case BVW_DVD_ROOT_MENU: + case BVW_DVD_TITLE_MENU: + case BVW_DVD_SUBPICTURE_MENU: + case BVW_DVD_AUDIO_MENU: + case BVW_DVD_ANGLE_MENU: + case BVW_DVD_CHAPTER_MENU: + /* FIXME */ + GST_WARNING ("FIXME: implement type %d", type); + break; + case BVW_DVD_NEXT_CHAPTER: + case BVW_DVD_PREV_CHAPTER: + case BVW_DVD_NEXT_TITLE: + case BVW_DVD_PREV_TITLE: + case BVW_DVD_NEXT_ANGLE: + case BVW_DVD_PREV_ANGLE: { + const gchar *fmt_name; + GstFormat fmt; + gint64 val; + gint dir; + + if (type == BVW_DVD_NEXT_CHAPTER || + type == BVW_DVD_NEXT_TITLE || + type == BVW_DVD_NEXT_ANGLE) + dir = 1; + else + dir = -1; + + if (type == BVW_DVD_NEXT_CHAPTER || type == BVW_DVD_PREV_CHAPTER) + fmt_name = "chapter"; + else if (type == BVW_DVD_NEXT_TITLE || type == BVW_DVD_PREV_TITLE) + fmt_name = "title"; + else + fmt_name = "angle"; + + fmt = gst_format_get_by_nick (fmt_name); + if (gst_element_query_position (bvw->priv->play, &fmt, &val)) { + GST_DEBUG ("current %s is: %" G_GINT64_FORMAT, fmt_name, val); + val += dir; + GST_DEBUG ("seeking to %s: %" G_GINT64_FORMAT, val); + gst_element_seek (bvw->priv->play, 1.0, fmt, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, val, GST_SEEK_TYPE_NONE, 0); + } else { + GST_DEBUG ("failed to query position (%s)", fmt_name); + } + break; + } + default: + GST_WARNING ("unhandled type %d", type); + break; + } +} + +void +bacon_video_widget_set_logo (BaconVideoWidget * bvw, gchar * filename) +{ + GError *error = NULL; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (filename != NULL); + + if (bvw->priv->logo_pixbuf != NULL) + g_object_unref (bvw->priv->logo_pixbuf); + + bvw->priv->logo_pixbuf = gdk_pixbuf_new_from_file (filename, &error); + + if (error) { + g_warning ("An error occurred trying to open logo %s: %s", + filename, error->message); + g_error_free (error); + } +} + +void +bacon_video_widget_set_logo_pixbuf (BaconVideoWidget * bvw, GdkPixbuf *logo) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (logo != NULL); + + if (bvw->priv->logo_pixbuf != NULL) + g_object_unref (bvw->priv->logo_pixbuf); + + g_object_ref (logo); + bvw->priv->logo_pixbuf = logo; +} + +void +bacon_video_widget_set_logo_mode (BaconVideoWidget * bvw, gboolean logo_mode) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + bvw->priv->logo_mode = logo_mode; + + if (bvw->priv->video_window) { + if (logo_mode) { + gdk_window_hide (bvw->priv->video_window); + } else { + gdk_window_show (bvw->priv->video_window); + } + } + + /* Queue a redraw of the widget */ + gtk_widget_queue_draw (GTK_WIDGET (bvw)); + + g_object_notify (G_OBJECT (bvw), "logo_mode"); +} + +gboolean +bacon_video_widget_get_logo_mode (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + + return bvw->priv->logo_mode; +} + +void +bacon_video_widget_pause (BaconVideoWidget * bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + g_return_if_fail (bvw->com->mrl != NULL); + + GST_LOG ("Pausing"); + gst_element_set_state (GST_ELEMENT (bvw->priv->play), GST_STATE_PAUSED); + bvw->priv->target_state = GST_STATE_PAUSED; +} + +void +bacon_video_widget_set_subtitle_font (BaconVideoWidget * bvw, + const gchar * font) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + if (!g_object_class_find_property (G_OBJECT_GET_CLASS (bvw->priv->play), "subtitle-font-desc")) + return; + g_object_set (bvw->priv->play, "subtitle-font-desc", font, NULL); +} + +void +bacon_video_widget_set_subtitle_encoding (BaconVideoWidget *bvw, + const char *encoding) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + if (!g_object_class_find_property (G_OBJECT_GET_CLASS (bvw->priv->play), "subtitle-encoding")) + return; + g_object_set (bvw->priv->play, "subtitle-encoding", encoding, NULL); +} + +gboolean +bacon_video_widget_can_set_volume (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + if (bvw->priv->speakersetup == BVW_AUDIO_SOUND_AC3PASSTHRU) + return FALSE; + + return !bvw->priv->uses_fakesink; +} + +void +bacon_video_widget_set_volume (BaconVideoWidget * bvw, int volume) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + if (bacon_video_widget_can_set_volume (bvw) != FALSE) + { + volume = CLAMP (volume, 0, 100); + g_object_set (bvw->priv->play, "volume", + (gdouble) (1. * volume / 100), NULL); + g_object_notify (G_OBJECT (bvw), "volume"); + } +} + +int +bacon_video_widget_get_volume (BaconVideoWidget * bvw) +{ + gdouble vol; + + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), -1); + + g_object_get (G_OBJECT (bvw->priv->play), "volume", &vol, NULL); + + return (gint) (vol * 100 + 0.5); +} + +gboolean +bacon_video_widget_fullscreen_mode_available (BaconVideoWidget *bvw, + TvOutType tvout) +{ + switch(tvout) { + case TV_OUT_NONE: + /* Assume that ordinary fullscreen always works */ + return TRUE; + case TV_OUT_NVTV_NTSC: + case TV_OUT_NVTV_PAL: +#ifdef HAVE_NVTV + /* Make sure nvtv is initialized, it will not do any harm + * if it is done twice any way */ + if (!(nvtv_simple_init() && nvtv_enable_autoresize(TRUE))) { + nvtv_simple_enable(FALSE); + } + return (nvtv_simple_is_available()); +#else + return FALSE; +#endif + } + return FALSE; +} + +void +bacon_video_widget_set_fullscreen (BaconVideoWidget * bvw, + gboolean fullscreen) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + if (bvw->priv->have_xvidmode == FALSE && + bvw->priv->tv_out_type != TV_OUT_NVTV_NTSC && + bvw->priv->tv_out_type != TV_OUT_NVTV_PAL) + return; + + bvw->priv->fullscreen_mode = fullscreen; + + if (fullscreen == FALSE) + { +#ifdef HAVE_NVTV + /* If NVTV is used */ + if (nvtv_simple_get_state() == NVTV_SIMPLE_TV_ON) { + nvtv_simple_switch(NVTV_SIMPLE_TV_OFF,0,0); + + /* Else if just auto resize is used */ + } else if (bvw->priv->auto_resize != FALSE) { +#endif + bacon_restore (); +#ifdef HAVE_NVTV + } + /* Turn fullscreen on with NVTV if that option is on */ + } else if ((bvw->priv->tv_out_type == TV_OUT_NVTV_NTSC) || + (bvw->priv->tv_out_type == TV_OUT_NVTV_PAL)) { + nvtv_simple_switch(NVTV_SIMPLE_TV_ON, + bvw->priv->video_width, + bvw->priv->video_height); +#endif + /* Turn fullscreen on when we have xvidmode */ + } else if (bvw->priv->have_xvidmode != FALSE) { + bacon_resize (); + } +} + +void +bacon_video_widget_set_show_cursor (BaconVideoWidget * bvw, + gboolean show_cursor) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + bvw->priv->cursor_shown = show_cursor; + + if (!GTK_WIDGET (bvw)->window) { + return; + } + + if (show_cursor == FALSE) { + totem_gdk_window_set_invisible_cursor (GTK_WIDGET (bvw)->window); + } else { + gdk_window_set_cursor (GTK_WIDGET (bvw)->window, NULL); + } +} + +gboolean +bacon_video_widget_get_show_cursor (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + + return bvw->priv->cursor_shown; +} + +void +bacon_video_widget_set_media_device (BaconVideoWidget * bvw, const char *path) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + /* FIXME: totally not thread-safe, used in the notify::source callback */ + g_free (bvw->priv->media_device); + bvw->priv->media_device = g_strdup (path); +} + +static void +get_visualization_size (BaconVideoWidget *bvw, + int *w, int *h, gint *fps_n, gint *fps_d) +{ + GdkScreen *screen; + int new_fps_n; + + if (bacon_video_widget_common_get_vis_quality (bvw->priv->visq, h, &new_fps_n) == FALSE) + return; + + screen = gtk_widget_get_screen (GTK_WIDGET (bvw)); + *w = *h * gdk_screen_get_width (screen) / gdk_screen_get_height (screen); + + if (fps_n) + *fps_n = new_fps_n; + if (fps_d) + *fps_d = 1; +} + +static GstElementFactory * +setup_vis_find_factory (BaconVideoWidget * bvw, const gchar * vis_name) +{ + GstElementFactory *fac = NULL; + GList *l, *features; + + features = get_visualization_features (); + + /* find element factory using long name */ + for (l = features; l != NULL; l = l->next) { + GstElementFactory *f = GST_ELEMENT_FACTORY (l->data); + + if (f && strcmp (vis_name, gst_element_factory_get_longname (f)) == 0) { + fac = f; + goto done; + } + } + + /* if nothing was found, try the short name (the default schema uses this) */ + for (l = features; l != NULL; l = l->next) { + GstElementFactory *f = GST_ELEMENT_FACTORY (l->data); + + /* set to long name as key so that the preferences dialog gets it right */ + if (f && strcmp (vis_name, GST_PLUGIN_FEATURE_NAME (f)) == 0) { + gconf_client_set_string (bvw->priv->gc, GCONF_PREFIX "/visual", + gst_element_factory_get_longname (f), NULL); + fac = f; + goto done; + } + } + +done: + g_list_free (features); + return fac; +} + +static void +setup_vis (BaconVideoWidget * bvw) +{ + GstElement *vis_bin = NULL; + + GST_DEBUG ("setup_vis called, show_vfx %d, vis element %s", + bvw->priv->show_vfx, bvw->priv->vis_element_name); + + if (bvw->priv->show_vfx && bvw->priv->vis_element_name) { + GstElement *vis_element = NULL, *vis_capsfilter = NULL; + GstPad *pad = NULL; + GstCaps *caps = NULL; + GstElementFactory *fac = NULL; + + fac = setup_vis_find_factory (bvw, bvw->priv->vis_element_name); + if (!fac) { + GST_DEBUG ("Could not find element factory for visualisation '%s'", + GST_STR_NULL (bvw->priv->vis_element_name)); + /* use goom as fallback, better than nothing */ + fac = setup_vis_find_factory (bvw, "goom"); + if (fac == NULL) { + goto beach; + } else { + GST_DEBUG ("Falling back on 'goom' for visualisation"); + } + } + + vis_element = gst_element_factory_create (fac, "vis_element"); + if (!GST_IS_ELEMENT (vis_element)) { + GST_DEBUG ("failed creating visualisation element"); + goto beach; + } + + vis_capsfilter = gst_element_factory_make ("capsfilter", + "vis_capsfilter"); + if (!GST_IS_ELEMENT (vis_capsfilter)) { + GST_DEBUG ("failed creating visualisation capsfilter element"); + gst_object_unref (vis_element); + goto beach; + } + + vis_bin = gst_bin_new ("vis_bin"); + if (!GST_IS_ELEMENT (vis_bin)) { + GST_DEBUG ("failed creating visualisation bin"); + gst_object_unref (vis_element); + gst_object_unref (vis_capsfilter); + goto beach; + } + + gst_bin_add_many (GST_BIN (vis_bin), vis_element, vis_capsfilter, NULL); + + /* Sink ghostpad */ + pad = gst_element_get_pad (vis_element, "sink"); + gst_element_add_pad (vis_bin, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + + /* Source ghostpad, link with vis_element */ + pad = gst_element_get_pad (vis_capsfilter, "src"); + gst_element_add_pad (vis_bin, gst_ghost_pad_new ("src", pad)); + gst_element_link_pads (vis_element, "src", vis_capsfilter, "sink"); + gst_object_unref (pad); + + /* Get allowed output caps from visualisation element */ + pad = gst_element_get_pad (vis_element, "src"); + caps = gst_pad_get_allowed_caps (pad); + gst_object_unref (pad); + + GST_DEBUG ("allowed caps: %" GST_PTR_FORMAT, caps); + + /* Can we fixate ? */ + if (caps && !gst_caps_is_fixed (caps)) { + guint i; + gint w, h, fps_n, fps_d; + + caps = gst_caps_make_writable (caps); + + /* Get visualization size */ + get_visualization_size (bvw, &w, &h, &fps_n, &fps_d); + + for (i = 0; i < gst_caps_get_size (caps); ++i) { + GstStructure *s = gst_caps_get_structure (caps, i); + + /* Fixate */ + gst_structure_fixate_field_nearest_int (s, "width", w); + gst_structure_fixate_field_nearest_int (s, "height", h); + gst_structure_fixate_field_nearest_fraction (s, "framerate", fps_n, + fps_d); + } + + /* set this */ + g_object_set (vis_capsfilter, "caps", caps, NULL); + } + + GST_DEBUG ("visualisation caps: %" GST_PTR_FORMAT, caps); + + if (GST_IS_CAPS (caps)) { + gst_caps_unref (caps); + } + } + + bvw->priv->vis_changed = FALSE; + +beach: + g_object_set (bvw->priv->play, "vis-plugin", vis_bin, NULL); + + return; +} + +gboolean +bacon_video_widget_set_show_visuals (BaconVideoWidget * bvw, + gboolean show_visuals) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + bvw->priv->show_vfx = show_visuals; + bvw->priv->vis_changed = TRUE; + + return TRUE; +} + +static gboolean +filter_features (GstPluginFeature * feature, gpointer data) +{ + GstElementFactory *f; + + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + f = GST_ELEMENT_FACTORY (feature); + if (!g_strrstr (gst_element_factory_get_klass (f), "Visualization")) + return FALSE; + + return TRUE; +} + +static GList * +get_visualization_features (void) +{ + return gst_registry_feature_filter (gst_registry_get_default (), + filter_features, FALSE, NULL); +} + +static void +add_longname (GstElementFactory *f, GList ** to) +{ + *to = g_list_append (*to, (gchar *) gst_element_factory_get_longname (f)); +} + +GList * +bacon_video_widget_get_visuals_list (BaconVideoWidget * bvw) +{ + GList *features, *names = NULL; + + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), NULL); + + if (bvw->priv->vis_plugins_list) { + return bvw->priv->vis_plugins_list; + } + + features = get_visualization_features (); + g_list_foreach (features, (GFunc) add_longname, &names); + g_list_free (features); + bvw->priv->vis_plugins_list = names; + + return names; +} + +gboolean +bacon_video_widget_set_visuals (BaconVideoWidget * bvw, const char *name) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + if (bvw->priv->vis_element_name) { + if (strcmp (bvw->priv->vis_element_name, name) == 0) { + return FALSE; + } + else { + g_free (bvw->priv->vis_element_name); + } + } + + bvw->priv->vis_element_name = g_strdup (name); + + GST_DEBUG ("new visualisation element name = '%s'", GST_STR_NULL (name)); + + setup_vis (bvw); + + return FALSE; +} + +void +bacon_video_widget_set_visuals_quality (BaconVideoWidget * bvw, + VisualsQuality quality) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + if (bvw->priv->visq == quality) + return; + + bvw->priv->visq = quality; + + setup_vis (bvw); +} + +gboolean +bacon_video_widget_get_auto_resize (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + + return bvw->priv->auto_resize; +} + +void +bacon_video_widget_set_auto_resize (BaconVideoWidget * bvw, + gboolean auto_resize) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + bvw->priv->auto_resize = auto_resize; + + /* this will take effect when the next media file loads */ +} + +void +bacon_video_widget_set_aspect_ratio (BaconVideoWidget *bvw, + BaconVideoWidgetAspectRatio ratio) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + bvw->priv->ratio_type = ratio; + got_video_size (bvw); +} + +BaconVideoWidgetAspectRatio +bacon_video_widget_get_aspect_ratio (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + + return bvw->priv->ratio_type; +} + +void +bacon_video_widget_set_scale_ratio (BaconVideoWidget * bvw, gfloat ratio) +{ + gint w, h; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + GST_DEBUG ("ratio = %.2f", ratio); + + get_media_size (bvw, &w, &h); + if (ratio == 0.0) { + if (totem_ratio_fits_screen (bvw->priv->video_window, w, h, 2.0)) + ratio = 2.0; + else if (totem_ratio_fits_screen (bvw->priv->video_window, w, h, 1.0)) + ratio = 1.0; + else if (totem_ratio_fits_screen (bvw->priv->video_window, w, h, 0.5)) + ratio = 0.5; + else + return; + } else { + if (!totem_ratio_fits_screen (bvw->priv->video_window, w, h, ratio)) { + GST_DEBUG ("movie doesn't fit on screen @ %.1fx (%dx%d)", w, h, ratio); + return; + } + } + w = (gfloat) w * ratio; + h = (gfloat) h * ratio; + + shrink_toplevel (bvw); + + GST_DEBUG ("setting preferred size %dx%d", w, h); + totem_widget_set_preferred_size (GTK_WIDGET (bvw), w, h); +} + +gboolean +bacon_video_widget_can_set_zoom (BaconVideoWidget *bvw) +{ + return FALSE; +} + +void +bacon_video_widget_set_zoom (BaconVideoWidget *bvw, + int zoom) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + /* implement me */ +} + +int +bacon_video_widget_get_zoom (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 100); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 100); + + return 100; +} + +int +bacon_video_widget_get_video_property (BaconVideoWidget *bvw, + BaconVideoWidgetVideoProperty type) +{ + int ret; + + g_return_val_if_fail (bvw != NULL, 65535/2); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 65535/2); + + g_mutex_lock (bvw->priv->lock); + + if (bvw->priv->balance && GST_IS_COLOR_BALANCE (bvw->priv->balance)) + { + const GList *channels_list = NULL; + GstColorBalanceChannel *found_channel = NULL; + + channels_list = gst_color_balance_list_channels (bvw->priv->balance); + + while (channels_list != NULL && found_channel == NULL) + { /* We search for the right channel corresponding to type */ + GstColorBalanceChannel *channel = channels_list->data; + + if (type == BVW_VIDEO_BRIGHTNESS && channel && + g_strrstr (channel->label, "BRIGHTNESS")) + { + g_object_ref (channel); + found_channel = channel; + } + else if (type == BVW_VIDEO_CONTRAST && channel && + g_strrstr (channel->label, "CONTRAST")) + { + g_object_ref (channel); + found_channel = channel; + } + else if (type == BVW_VIDEO_SATURATION && channel && + g_strrstr (channel->label, "SATURATION")) + { + g_object_ref (channel); + found_channel = channel; + } + else if (type == BVW_VIDEO_HUE && channel && + g_strrstr (channel->label, "HUE")) + { + g_object_ref (channel); + found_channel = channel; + } + channels_list = g_list_next (channels_list); + } + + if (found_channel && GST_IS_COLOR_BALANCE_CHANNEL (found_channel)) { + gint cur; + + cur = gst_color_balance_get_value (bvw->priv->balance, + found_channel); + + GST_DEBUG ("channel %s: cur=%d, min=%d, max=%d", found_channel->label, + cur, found_channel->min_value, found_channel->max_value); + + ret = ((double) cur - found_channel->min_value) * 65535 / + ((double) found_channel->max_value - found_channel->min_value); + + GST_DEBUG ("channel %s: returning value %d", found_channel->label, ret); + g_object_unref (found_channel); + goto done; + } + } + + /* value wasn't found, get from gconf */ + ret = gconf_client_get_int (bvw->priv->gc, video_props_str[type], NULL); + + GST_DEBUG ("nothing found for type %d, returning value %d from gconf key %s", + type, ret, video_props_str[type]); + +done: + + g_mutex_unlock (bvw->priv->lock); + return ret; +} + +void +bacon_video_widget_set_video_property (BaconVideoWidget *bvw, + BaconVideoWidgetVideoProperty type, + int value) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + GST_DEBUG ("set video property type %d to value %d", type, value); + + if ( !(value < 65535 && value > 0) ) + return; + + if (bvw->priv->balance && GST_IS_COLOR_BALANCE (bvw->priv->balance)) + { + const GList *channels_list = NULL; + GstColorBalanceChannel *found_channel = NULL; + + channels_list = gst_color_balance_list_channels (bvw->priv->balance); + + while (found_channel == NULL && channels_list != NULL) { + /* We search for the right channel corresponding to type */ + GstColorBalanceChannel *channel = channels_list->data; + + if (type == BVW_VIDEO_BRIGHTNESS && channel && + g_strrstr (channel->label, "BRIGHTNESS")) + { + g_object_ref (channel); + found_channel = channel; + } + else if (type == BVW_VIDEO_CONTRAST && channel && + g_strrstr (channel->label, "CONTRAST")) + { + g_object_ref (channel); + found_channel = channel; + } + else if (type == BVW_VIDEO_SATURATION && channel && + g_strrstr (channel->label, "SATURATION")) + { + g_object_ref (channel); + found_channel = channel; + } + else if (type == BVW_VIDEO_HUE && channel && + g_strrstr (channel->label, "HUE")) + { + g_object_ref (channel); + found_channel = channel; + } + channels_list = g_list_next (channels_list); + } + + if (found_channel && GST_IS_COLOR_BALANCE_CHANNEL (found_channel)) + { + int i_value = value * ((double) found_channel->max_value - + found_channel->min_value) / 65535 + found_channel->min_value; + + GST_DEBUG ("channel %s: set to %d/65535", found_channel->label, value); + + gst_color_balance_set_value (bvw->priv->balance, found_channel, + i_value); + + GST_DEBUG ("channel %s: val=%d, min=%d, max=%d", found_channel->label, + i_value, found_channel->min_value, found_channel->max_value); + + g_object_unref (found_channel); + } + } + + /* save in gconf */ + gconf_client_set_int (bvw->priv->gc, video_props_str[type], value, NULL); + + GST_DEBUG ("setting value %d on gconf key %s", value, video_props_str[type]); +} + +float +bacon_video_widget_get_position (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + return bvw->priv->current_position; +} + +gint64 +bacon_video_widget_get_current_time (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + return bvw->priv->current_time; +} + +gint64 +bacon_video_widget_get_stream_length (BaconVideoWidget * bvw) +{ + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + + if (bvw->priv->stream_length == 0 && bvw->priv->play != NULL) { + GstFormat fmt = GST_FORMAT_TIME; + gint64 len = -1; + + if (gst_element_query_duration (bvw->priv->play, &fmt, &len) && len != -1) { + bvw->priv->stream_length = len / GST_MSECOND; + } + } + + return bvw->priv->stream_length; +} + +gboolean +bacon_video_widget_is_playing (BaconVideoWidget * bvw) +{ + gboolean ret; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + ret = (bvw->priv->target_state == GST_STATE_PLAYING); + GST_LOG ("%splaying", (ret) ? "" : "not "); + + return ret; +} + +gboolean +bacon_video_widget_is_seekable (BaconVideoWidget * bvw) +{ + gboolean res; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + if (bvw->priv->seekable == -1) { + GstQuery *query; + + query = gst_query_new_seeking (GST_FORMAT_TIME); + if (gst_element_query (bvw->priv->play, query)) { + gst_query_parse_seeking (query, NULL, &res, NULL, NULL); + bvw->priv->seekable = (res) ? 1 : 0; + } else { + GST_DEBUG ("seeking query failed"); + } + gst_query_unref (query); + } + + if (bvw->priv->seekable != -1) { + res = (bvw->priv->seekable != 0); + goto done; + } + + /* try to guess from duration (this is very unreliable though) */ + if (bvw->priv->stream_length == 0) { + res = (bacon_video_widget_get_stream_length (bvw) > 0); + } else { + res = (bvw->priv->stream_length > 0); + } + +done: + + GST_DEBUG ("stream is%s seekable", (res) ? "" : " not"); + return res; +} + +gboolean +bacon_video_widget_can_play (BaconVideoWidget * bvw, MediaType type) +{ + gboolean res; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + switch (type) { + case MEDIA_TYPE_CDDA: + case MEDIA_TYPE_VCD: + res = TRUE; + break; + case MEDIA_TYPE_DVD: + default: + res = FALSE; + break; + } + + GST_DEBUG ("type=%d, can_play=%s", type, (res) ? "TRUE" : "FALSE"); + return res; +} + +gchar ** +bacon_video_widget_get_mrls (BaconVideoWidget * bvw, MediaType type) +{ + gchar **mrls; + + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), NULL); + + GST_DEBUG ("type = %d", type); + + switch (type) { + case MEDIA_TYPE_CDDA: { + GstStateChangeReturn ret; + GstElement *cddasrc; + GstFormat fmt; + GstPad *pad; + gint64 num_tracks = 0; + gchar *uri[] = { "cdda://", NULL }; + gint i; + + GST_DEBUG ("Checking for Audio CD sources (cdda://) ..."); + cddasrc = gst_element_make_from_uri (GST_URI_SRC, "cdda://1", NULL); + if (!cddasrc) { + GST_DEBUG ("No Audio CD source plugins found"); + return NULL; + } + + fmt = gst_format_get_by_nick ("track"); + if (!fmt) { + gst_object_unref (cddasrc); + return NULL; + } + + /* FIXME: what about setting the device? */ + + GST_DEBUG ("Opening CD and getting number of tracks ..."); + + /* wait for state change to complete or fail */ + gst_element_set_state (cddasrc, GST_STATE_PAUSED); + ret = gst_element_get_state (cddasrc, NULL, NULL, -1); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_DEBUG ("Couldn't set cdda source to PAUSED"); + gst_element_set_state (cddasrc, GST_STATE_NULL); + gst_object_unref (cddasrc); + return NULL; + } + + pad = gst_element_get_pad (cddasrc, "src"); + if (gst_pad_query_duration (pad, &fmt, &num_tracks) && num_tracks > 0) { + GST_DEBUG ("%" G_GINT64_FORMAT " tracks", num_tracks); + mrls = g_new0 (gchar *, num_tracks + 1); + for (i = 1; i <= num_tracks; ++i) { + mrls[i-1] = g_strdup_printf ("cdda://%d", i); + } + } else { + GST_DEBUG ("could not query track number"); + mrls = g_strdupv (uri); + } + gst_object_unref (pad); + + gst_element_set_state (cddasrc, GST_STATE_NULL); + gst_object_unref (cddasrc); + break; + } + case MEDIA_TYPE_VCD: { + gchar *uri[] = { "vcd://", NULL }; + mrls = g_strdupv (uri); + break; + } +/* + case MEDIA_TYPE_DVD: { + gchar *uri[] = { "dvd://", NULL }; + mrls = g_strdupv (uri); + break; + } +*/ + default: + mrls = NULL; + break; + } + + return mrls; +} + +static struct _metadata_map_info { + BaconVideoWidgetMetadataType type; + const gchar *str; +} metadata_str_map[] = { + { BVW_INFO_TITLE, "title" }, + { BVW_INFO_ARTIST, "artist" }, + { BVW_INFO_YEAR, "year" }, + { BVW_INFO_ALBUM, "album" }, + { BVW_INFO_DURATION, "duration" }, + { BVW_INFO_TRACK_NUMBER, "track-number" }, + { BVW_INFO_HAS_VIDEO, "has-video" }, + { BVW_INFO_DIMENSION_X, "dimension-x" }, + { BVW_INFO_DIMENSION_Y, "dimension-y" }, + { BVW_INFO_VIDEO_BITRATE, "video-bitrate" }, + { BVW_INFO_VIDEO_CODEC, "video-codec" }, + { BVW_INFO_FPS, "fps" }, + { BVW_INFO_HAS_AUDIO, "has-audio" }, + { BVW_INFO_AUDIO_BITRATE, "audio-bitrate" }, + { BVW_INFO_AUDIO_CODEC, "audio-codec" }, + { BVW_INFO_AUDIO_SAMPLE_RATE, "samplerate" }, + { BVW_INFO_AUDIO_CHANNELS, "channels" } +}; + +static const gchar * +get_metadata_type_name (BaconVideoWidgetMetadataType type) +{ + guint i; + for (i = 0; i < G_N_ELEMENTS (metadata_str_map); ++i) { + if (metadata_str_map[i].type == type) + return metadata_str_map[i].str; + } + return "unknown"; +} + +static GObject * +bvw_get_stream_info_of_current_stream (BaconVideoWidget * bvw, + const gchar *stream_type) +{ + GObject *current_info; + GList *streams; + gchar *lower, *cur_prop_str; + gint stream_num = -1; + + if (bvw->priv->play == NULL) + return NULL; + + lower = g_ascii_strdown (stream_type, -1); + cur_prop_str = g_strconcat ("current-", lower, NULL); + g_object_get (bvw->priv->play, cur_prop_str, &stream_num, NULL); + g_free (cur_prop_str); + g_free (lower); + + GST_LOG ("current %s stream: %d", stream_type, stream_num); + if (stream_num < 0) + return NULL; + + streams = get_stream_info_objects_for_type (bvw, stream_type); + current_info = g_list_nth_data (streams, stream_num); + if (current_info != NULL) + g_object_ref (current_info); + g_list_foreach (streams, (GFunc) g_object_unref, NULL); + g_list_free (streams); + GST_LOG ("current %s stream info object %p", stream_type, current_info); + return current_info; +} + +static GstCaps * +bvw_get_caps_of_current_stream (BaconVideoWidget * bvw, + const gchar *stream_type) +{ + GstCaps *caps = NULL; + GObject *current; + + current = bvw_get_stream_info_of_current_stream (bvw, stream_type); + if (current != NULL) { + GstObject *obj = NULL; + + /* we get the caps from the pad here instead of using the "caps" property + * directly since the latter will not give us fixed/negotiated caps + * (playbin bug as of gst-plugins-base 0.10.10) */ + g_object_get (G_OBJECT (current), "object", &obj, NULL); + if (obj) { + if (GST_IS_PAD (obj)) { + caps = gst_pad_get_negotiated_caps (GST_PAD_CAST (obj)); + } + gst_object_unref (obj); + } + gst_object_unref (current); + } + GST_LOG ("current %s stream caps: %" GST_PTR_FORMAT, stream_type, caps); + return caps; +} + +static gboolean +audio_caps_have_LFE (GstStructure * s) +{ + GstAudioChannelPosition *positions; + gint i, channels; + + if (!gst_structure_get_value (s, "channel-positions") || + !gst_structure_get_int (s, "channels", &channels)) { + return FALSE; + } + + positions = gst_audio_get_channel_positions (s); + if (positions == NULL) + return FALSE; + + for (i = 0; i < channels; ++i) { + if (positions[i] == GST_AUDIO_CHANNEL_POSITION_LFE) { + g_free (positions); + return TRUE; + } + } + + g_free (positions); + return FALSE; +} + +static void +bacon_video_widget_get_metadata_string (BaconVideoWidget * bvw, + BaconVideoWidgetMetadataType type, + GValue * value) +{ + char *string = NULL; + gboolean res = FALSE; + + g_value_init (value, G_TYPE_STRING); + + if (bvw->priv->play == NULL || bvw->priv->tagcache == NULL) + { + g_value_set_string (value, NULL); + return; + } + + switch (type) + { + case BVW_INFO_TITLE: + res = gst_tag_list_get_string_index (bvw->priv->tagcache, + GST_TAG_TITLE, 0, &string); + break; + case BVW_INFO_ARTIST: + res = gst_tag_list_get_string_index (bvw->priv->tagcache, + GST_TAG_ARTIST, 0, &string); + break; + case BVW_INFO_YEAR: { + GDate *date; + if ((res = gst_tag_list_get_date (bvw->priv->tagcache, + GST_TAG_DATE, &date))) { + string = g_strdup_printf ("%d", g_date_get_year (date)); + g_date_free (date); + } + break; + } + case BVW_INFO_ALBUM: + res = gst_tag_list_get_string_index (bvw->priv->tagcache, + GST_TAG_ALBUM, 0, &string); + break; + case BVW_INFO_VIDEO_CODEC: { + GObject *info; + + /* try to get this from the stream info first */ + if ((info = bvw_get_stream_info_of_current_stream (bvw, "video"))) { + g_object_get (info, "codec", &string, NULL); + res = (string != NULL); + gst_object_unref (info); + } + + /* if that didn't work, try the aggregated tags */ + if (!res) { + res = gst_tag_list_get_string (bvw->priv->tagcache, + GST_TAG_VIDEO_CODEC, &string); + } + break; + } + case BVW_INFO_AUDIO_CODEC: { + GObject *info; + + /* try to get this from the stream info first */ + if ((info = bvw_get_stream_info_of_current_stream (bvw, "audio"))) { + g_object_get (info, "codec", &string, NULL); + res = (string != NULL); + gst_object_unref (info); + } + + /* if that didn't work, try the aggregated tags */ + if (!res) { + res = gst_tag_list_get_string (bvw->priv->tagcache, + GST_TAG_AUDIO_CODEC, &string); + } + break; + } + case BVW_INFO_AUDIO_CHANNELS: { + GstStructure *s; + GstCaps *caps; + + caps = bvw_get_caps_of_current_stream (bvw, "audio"); + if (caps) { + gint channels = 0; + + s = gst_caps_get_structure (caps, 0); + if ((res = gst_structure_get_int (s, "channels", &channels))) { + /* FIXME: do something more sophisticated - but what? */ + if (channels > 2 && audio_caps_have_LFE (s)) { + string = g_strdup_printf ("%d.1", channels - 1); + } else { + string = g_strdup_printf ("%d", channels); + } + } + gst_caps_unref (caps); + } + break; + } + default: + g_assert_not_reached (); + } + + if (res && string && g_utf8_validate (string, -1, NULL)) { + g_value_take_string (value, string); + GST_DEBUG ("%s = '%s'", get_metadata_type_name (type), string); + } else { + g_value_set_string (value, NULL); + g_free (string); + } + + return; +} + +static void +bacon_video_widget_get_metadata_int (BaconVideoWidget * bvw, + BaconVideoWidgetMetadataType type, + GValue * value) +{ + int integer = 0; + + g_value_init (value, G_TYPE_INT); + + if (bvw->priv->play == NULL) + { + g_value_set_int (value, 0); + return; + } + + switch (type) + { + case BVW_INFO_DURATION: + integer = bacon_video_widget_get_stream_length (bvw) / 1000; + break; + case BVW_INFO_TRACK_NUMBER: + if (!gst_tag_list_get_uint (bvw->priv->tagcache, + GST_TAG_TRACK_NUMBER, (guint *) &integer)) + integer = 0; + break; + case BVW_INFO_DIMENSION_X: + integer = bvw->priv->video_width; + break; + case BVW_INFO_DIMENSION_Y: + integer = bvw->priv->video_height; + break; + case BVW_INFO_FPS: + if (bvw->priv->video_fps_d > 0) { + /* Round up/down to the nearest integer framerate */ + integer = (bvw->priv->video_fps_n + bvw->priv->video_fps_d/2) / + bvw->priv->video_fps_d; + } + else + integer = 0; + break; + case BVW_INFO_AUDIO_BITRATE: + if (bvw->priv->audiotags == NULL) + break; + if (gst_tag_list_get_uint (bvw->priv->audiotags, GST_TAG_BITRATE, + (guint *)&integer) || + gst_tag_list_get_uint (bvw->priv->audiotags, GST_TAG_NOMINAL_BITRATE, + (guint *)&integer)) { + integer /= 1000; + } + break; + case BVW_INFO_VIDEO_BITRATE: + if (bvw->priv->videotags == NULL) + break; + if (gst_tag_list_get_uint (bvw->priv->videotags, GST_TAG_BITRATE, + (guint *)&integer) || + gst_tag_list_get_uint (bvw->priv->videotags, GST_TAG_NOMINAL_BITRATE, + (guint *)&integer)) { + integer /= 1000; + } + break; + case BVW_INFO_AUDIO_SAMPLE_RATE: { + GstStructure *s; + GstCaps *caps; + + caps = bvw_get_caps_of_current_stream (bvw, "audio"); + if (caps) { + s = gst_caps_get_structure (caps, 0); + gst_structure_get_int (s, "rate", &integer); + gst_caps_unref (caps); + } + break; + } + default: + g_assert_not_reached (); + } + + g_value_set_int (value, integer); + GST_DEBUG ("%s = %d", get_metadata_type_name (type), integer); + + return; +} + +static void +bacon_video_widget_get_metadata_bool (BaconVideoWidget * bvw, + BaconVideoWidgetMetadataType type, + GValue * value) +{ + gboolean boolean = FALSE; + + g_value_init (value, G_TYPE_BOOLEAN); + + if (bvw->priv->play == NULL) { + g_value_set_boolean (value, FALSE); + return; + } + + GST_DEBUG ("tagcache = %" GST_PTR_FORMAT, bvw->priv->tagcache); + GST_DEBUG ("videotags = %" GST_PTR_FORMAT, bvw->priv->videotags); + GST_DEBUG ("audiotags = %" GST_PTR_FORMAT, bvw->priv->audiotags); + + switch (type) + { + case BVW_INFO_HAS_VIDEO: + boolean = bvw->priv->media_has_video; + /* if properties dialog, show the metadata we + * have even if we cannot decode the stream */ + if (!boolean && bvw->priv->use_type == BVW_USE_TYPE_METADATA && + bvw->priv->tagcache != NULL && + gst_structure_has_field ((GstStructure *) bvw->priv->tagcache, + GST_TAG_VIDEO_CODEC)) { + boolean = TRUE; + } + break; + case BVW_INFO_HAS_AUDIO: + boolean = bvw->priv->media_has_audio; + /* if properties dialog, show the metadata we + * have even if we cannot decode the stream */ + if (!boolean && bvw->priv->use_type == BVW_USE_TYPE_METADATA && + bvw->priv->tagcache != NULL && + gst_structure_has_field ((GstStructure *) bvw->priv->tagcache, + GST_TAG_AUDIO_CODEC)) { + boolean = TRUE; + } + break; + default: + g_assert_not_reached (); + } + + g_value_set_boolean (value, boolean); + GST_DEBUG ("%s = %s", get_metadata_type_name (type), (boolean) ? "yes" : "no"); + + return; +} + +static void +bvw_process_pending_tag_messages (BaconVideoWidget * bvw) +{ + GstMessageType events; + GstMessage *msg; + GstBus *bus; + + /* process any pending tag messages on the bus NOW, so we can get to + * the information without/before giving control back to the main loop */ + + /* application message is for stream-info */ + events = GST_MESSAGE_TAG | GST_MESSAGE_DURATION | GST_MESSAGE_APPLICATION; + bus = gst_element_get_bus (bvw->priv->play); + while ((msg = gst_bus_poll (bus, events, 0))) { + gst_bus_async_signal_func (bus, msg, NULL); + } + gst_object_unref (bus); +} + +void +bacon_video_widget_get_metadata (BaconVideoWidget * bvw, + BaconVideoWidgetMetadataType type, + GValue * value) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (GST_IS_ELEMENT (bvw->priv->play)); + + switch (type) + { + case BVW_INFO_TITLE: + case BVW_INFO_ARTIST: + case BVW_INFO_YEAR: + case BVW_INFO_ALBUM: + case BVW_INFO_VIDEO_CODEC: + case BVW_INFO_AUDIO_CODEC: + case BVW_INFO_AUDIO_CHANNELS: + bacon_video_widget_get_metadata_string (bvw, type, value); + break; + case BVW_INFO_DURATION: + case BVW_INFO_DIMENSION_X: + case BVW_INFO_DIMENSION_Y: + case BVW_INFO_FPS: + case BVW_INFO_AUDIO_BITRATE: + case BVW_INFO_VIDEO_BITRATE: + case BVW_INFO_TRACK_NUMBER: + case BVW_INFO_AUDIO_SAMPLE_RATE: + bacon_video_widget_get_metadata_int (bvw, type, value); + break; + case BVW_INFO_HAS_VIDEO: + case BVW_INFO_HAS_AUDIO: + bacon_video_widget_get_metadata_bool (bvw, type, value); + break; + default: + g_return_if_reached (); + } + + return; +} + +/* Screenshot functions */ +gboolean +bacon_video_widget_can_get_frames (BaconVideoWidget * bvw, GError ** error) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE); + + /* check for version */ + if (!g_object_class_find_property ( + G_OBJECT_GET_CLASS (bvw->priv->play), "frame")) { + g_set_error (error, BVW_ERROR, BVW_ERROR_GENERIC, + _("Too old version of GStreamer installed.")); + return FALSE; + } + + /* check for video */ + if (!bvw->priv->media_has_video) { + g_set_error (error, BVW_ERROR, BVW_ERROR_GENERIC, + _("Media contains no supported video streams.")); + } + + return bvw->priv->media_has_video; +} + +static void +destroy_pixbuf (guchar *pix, gpointer data) +{ + gst_buffer_unref (GST_BUFFER (data)); +} + +GdkPixbuf * +bacon_video_widget_get_current_frame (BaconVideoWidget * bvw) +{ + GstStructure *s; + GstBuffer *buf = NULL; + GdkPixbuf *pixbuf; + GstCaps *to_caps; + gint outwidth = 0; + gint outheight = 0; + + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), NULL); + + /* when used as thumbnailer, wait for pending seeks to complete */ + if (bvw->priv->use_type == BVW_USE_TYPE_CAPTURE) { + gst_element_get_state (bvw->priv->play, NULL, NULL, -1); + } + + /* no video info */ + if (!bvw->priv->video_width || !bvw->priv->video_height) { + GST_DEBUG ("Could not take screenshot: %s", "no video info"); + g_warning ("Could not take screenshot: %s", "no video info"); + return NULL; + } + + /* get frame */ + g_object_get (bvw->priv->play, "frame", &buf, NULL); + + if (!buf) { + GST_DEBUG ("Could not take screenshot: %s", "no last video frame"); + g_warning ("Could not take screenshot: %s", "no last video frame"); + return NULL; + } + + if (GST_BUFFER_CAPS (buf) == NULL) { + GST_DEBUG ("Could not take screenshot: %s", "no caps on buffer"); + g_warning ("Could not take screenshot: %s", "no caps on buffer"); + return NULL; + } + + /* convert to our desired format (RGB24) */ + to_caps = gst_caps_new_simple ("video/x-raw-rgb", + "bpp", G_TYPE_INT, 24, + "depth", G_TYPE_INT, 24, + /* Note: we don't ask for a specific width/height here, so that + * videoscale can adjust dimensions from a non-1/1 pixel aspect + * ratio to a 1/1 pixel-aspect-ratio */ + "framerate", GST_TYPE_FRACTION, + bvw->priv->video_fps_n, bvw->priv->video_fps_d, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + "endianness", G_TYPE_INT, G_BIG_ENDIAN, + "red_mask", G_TYPE_INT, 0xff0000, + "green_mask", G_TYPE_INT, 0x00ff00, + "blue_mask", G_TYPE_INT, 0x0000ff, + NULL); + + GST_DEBUG ("frame caps: %" GST_PTR_FORMAT, GST_BUFFER_CAPS (buf)); + GST_DEBUG ("pixbuf caps: %" GST_PTR_FORMAT, to_caps); + + /* bvw_frame_conv_convert () takes ownership of the buffer passed */ + buf = bvw_frame_conv_convert (buf, to_caps); + + gst_caps_unref (to_caps); + + if (!buf) { + GST_DEBUG ("Could not take screenshot: %s", "conversion failed"); + g_warning ("Could not take screenshot: %s", "conversion failed"); + return NULL; + } + + if (!GST_BUFFER_CAPS (buf)) { + GST_DEBUG ("Could not take screenshot: %s", "no caps on output buffer"); + g_warning ("Could not take screenshot: %s", "no caps on output buffer"); + return NULL; + } + + s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0); + gst_structure_get_int (s, "width", &outwidth); + gst_structure_get_int (s, "height", &outheight); + g_return_val_if_fail (outwidth > 0 && outheight > 0, FALSE); + + /* create pixbuf from that - use our own destroy function */ + pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buf), + GDK_COLORSPACE_RGB, FALSE, 8, outwidth, outheight, + GST_ROUND_UP_4 (outwidth * 3), destroy_pixbuf, buf); + + if (!pixbuf) { + GST_DEBUG ("Could not take screenshot: %s", "could not create pixbuf"); + g_warning ("Could not take screenshot: %s", "could not create pixbuf"); + gst_buffer_unref (buf); + } + + return pixbuf; +} + +static void +cb_gconf (GConfClient * client, + guint connection_id, + GConfEntry * entry, + gpointer data) +{ + BaconVideoWidget *bvw = data; + + if (!strcmp (entry->key, "/apps/totem/network-buffer-threshold")) { + g_object_set (bvw->priv->play, "queue-threshold", + (guint64) (GST_SECOND * gconf_value_get_float (entry->value)), NULL); + } else if (!strcmp (entry->key, "/apps/totem/buffer-size")) { + g_object_set (bvw->priv->play, "queue-size", + (guint64) (GST_SECOND * gconf_value_get_float (entry->value)), NULL); + } +} + +/* =========================================== */ +/* */ +/* Widget typing & Creation */ +/* */ +/* =========================================== */ + +G_DEFINE_TYPE(BaconVideoWidget, bacon_video_widget, GTK_TYPE_BOX) + +/* applications must use exactly one of bacon_video_widget_get_option_group() + * OR bacon_video_widget_init_backend(), but not both */ + +GOptionGroup* +bacon_video_widget_get_option_group (void) +{ + return gst_init_get_option_group (); +} + +void +bacon_video_widget_init_backend (int *argc, char ***argv) +{ + gst_init (argc, argv); +} + +GQuark +bacon_video_widget_error_quark (void) +{ + static GQuark q; /* 0 */ + + if (q == 0) { + q = g_quark_from_static_string ("bvw-error-quark"); + } + return q; +} + +/* fold function to pick the best colorspace element */ +static gboolean +find_colorbalance_element (GstElement *element, GValue * ret, GstElement **cb) +{ + GstColorBalanceClass *cb_class; + + GST_DEBUG ("Checking element %s ...", GST_OBJECT_NAME (element)); + + if (!GST_IS_COLOR_BALANCE (element)) + return TRUE; + + GST_DEBUG ("Element %s is a color balance", GST_OBJECT_NAME (element)); + + cb_class = GST_COLOR_BALANCE_GET_CLASS (element); + if (GST_COLOR_BALANCE_TYPE (cb_class) == GST_COLOR_BALANCE_HARDWARE) { + gst_object_replace ((GstObject **) cb, (GstObject *) element); + /* shortcuts the fold */ + return FALSE; + } else if (*cb == NULL) { + gst_object_replace ((GstObject **) cb, (GstObject *) element); + return TRUE; + } else { + return TRUE; + } +} + +static void +bvw_update_interface_implementations (BaconVideoWidget *bvw) +{ + GstColorBalance *old_balance = bvw->priv->balance; + GstXOverlay *old_xoverlay = bvw->priv->xoverlay; + GConfValue *confvalue; + GstElement *video_sink = NULL; + GstElement *element = NULL; + GstIteratorResult ires; + GstIterator *iter; + gint i; + + g_object_get (bvw->priv->play, "video-sink", &video_sink, NULL); + g_assert (video_sink != NULL); + + /* We try to get an element supporting XOverlay interface */ + if (GST_IS_BIN (video_sink)) { + GST_DEBUG ("Retrieving xoverlay from bin ..."); + element = gst_bin_get_by_interface (GST_BIN (video_sink), + GST_TYPE_X_OVERLAY); + } else { + element = video_sink; + } + + if (GST_IS_X_OVERLAY (element)) { + GST_DEBUG ("Found xoverlay: %s", GST_OBJECT_NAME (element)); + bvw->priv->xoverlay = GST_X_OVERLAY (element); + } else { + GST_DEBUG ("No xoverlay found"); + bvw->priv->xoverlay = NULL; + } + + /* Find best color balance element (using custom iterator so + * we can prefer hardware implementations to software ones) */ + + /* FIXME: this doesn't work reliably yet, most of the time + * the fold function doesn't even get called, while sometimes + * it does ... */ + iter = gst_bin_iterate_all_by_interface (GST_BIN (bvw->priv->play), + GST_TYPE_COLOR_BALANCE); + /* naively assume no resync */ + element = NULL; + ires = gst_iterator_fold (iter, + (GstIteratorFoldFunction) find_colorbalance_element, NULL, &element); + gst_iterator_free (iter); + + if (element) { + bvw->priv->balance = GST_COLOR_BALANCE (element); + GST_DEBUG ("Best colorbalance found: %s", + GST_OBJECT_NAME (bvw->priv->balance)); + } else if (GST_IS_COLOR_BALANCE (bvw->priv->xoverlay)) { + bvw->priv->balance = GST_COLOR_BALANCE (bvw->priv->xoverlay); + gst_object_ref (bvw->priv->balance); + GST_DEBUG ("Colorbalance backup found: %s", + GST_OBJECT_NAME (bvw->priv->balance)); + } else { + GST_DEBUG ("No colorbalance found"); + bvw->priv->balance = NULL; + } + + /* Setup brightness and contrast */ + for (i = 0; i < 4; i++) { + confvalue = gconf_client_get_without_default (bvw->priv->gc, + video_props_str[i], NULL); + if (confvalue != NULL) { + bacon_video_widget_set_video_property (bvw, i, + gconf_value_get_int (confvalue)); + gconf_value_free (confvalue); + } + } + + if (old_xoverlay) + gst_object_unref (GST_OBJECT (old_xoverlay)); + + if (old_balance) + gst_object_unref (GST_OBJECT (old_balance)); + + gst_object_unref (video_sink); +} + +static void +bvw_element_msg_sync (GstBus *bus, GstMessage *msg, gpointer data) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data); + + g_assert (msg->type == GST_MESSAGE_ELEMENT); + + if (msg->structure == NULL) + return; + + /* This only gets sent if we haven't set an ID yet. This is our last + * chance to set it before the video sink will create its own window */ + if (gst_structure_has_name (msg->structure, "prepare-xwindow-id")) { + XID window; + + GST_DEBUG ("Handling sync prepare-xwindow-id message"); + + g_mutex_lock (bvw->priv->lock); + bvw_update_interface_implementations (bvw); + g_mutex_unlock (bvw->priv->lock); + + g_return_if_fail (bvw->priv->xoverlay != NULL); + g_return_if_fail (bvw->priv->video_window != NULL); + + window = GDK_WINDOW_XWINDOW (bvw->priv->video_window); + gst_x_overlay_set_xwindow_id (bvw->priv->xoverlay, window); + } +} + +static void +got_new_video_sink_bin_element (GstBin *video_sink, GstElement *element, + gpointer data) +{ + BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data); + + g_mutex_lock (bvw->priv->lock); + bvw_update_interface_implementations (bvw); + g_mutex_unlock (bvw->priv->lock); +} + +GtkWidget * +bacon_video_widget_new (int width, int height, + BvwUseType type, GError ** err) +{ + GConfValue *confvalue; + BaconVideoWidget *bvw; + GstElement *audio_sink = NULL, *video_sink = NULL; + + if (_totem_gst_debug_cat == NULL) { + gchar *version_str; + + GST_DEBUG_CATEGORY_INIT (_totem_gst_debug_cat, "totem", 0, + "Totem GStreamer Backend"); + + version_str = gst_version_string (); + GST_DEBUG ("Initialised %s", version_str); + g_free (version_str); + + gst_base_utils_init (); + } + + bvw = BACON_VIDEO_WIDGET (g_object_new + (bacon_video_widget_get_type (), NULL)); + + bvw->priv->use_type = type; + GST_DEBUG ("use_type = %d", type); + + bvw->priv->play = gst_element_factory_make ("playbin", "play"); + if (!bvw->priv->play) { + g_set_error (err, BVW_ERROR, BVW_ERROR_PLUGIN_LOAD, + _("Failed to create a GStreamer play object. " + "Please check your GStreamer installation.")); + g_object_ref_sink (bvw); + g_object_unref (bvw); + return NULL; + } + + bvw->priv->bus = gst_element_get_bus (bvw->priv->play); + + gst_bus_add_signal_watch (bvw->priv->bus); + + bvw->priv->sig_bus_async = + g_signal_connect (bvw->priv->bus, "message", + G_CALLBACK (bvw_bus_message_cb), + bvw); + + bvw->priv->speakersetup = BVW_AUDIO_SOUND_STEREO; + bvw->priv->media_device = g_strdup ("/dev/dvd"); + bvw->priv->init_width = 240; + bvw->priv->init_height = 180; + bvw->priv->visq = VISUAL_SMALL; + bvw->priv->show_vfx = FALSE; + bvw->priv->vis_element_name = g_strdup ("goom"); + bvw->priv->tv_out_type = TV_OUT_NONE; + bvw->priv->connection_speed = 0; + bvw->priv->ratio_type = BVW_RATIO_AUTO; + + bvw->priv->cursor_shown = TRUE; + bvw->priv->logo_mode = FALSE; + bvw->priv->auto_resize = TRUE; + + /* gconf setting in backend */ + bvw->priv->gc = gconf_client_get_default (); + gconf_client_notify_add (bvw->priv->gc, "/apps/totem", + cb_gconf, bvw, NULL, NULL); + + if (type == BVW_USE_TYPE_VIDEO || type == BVW_USE_TYPE_AUDIO) { + audio_sink = gst_element_factory_make ("gconfaudiosink", "audio-sink"); + if (audio_sink == NULL) { + g_warning ("Could not create element 'gconfaudiosink'"); + /* Try to fallback on autoaudiosink */ + audio_sink = gst_element_factory_make ("autoaudiosink", "audio-sink"); + } else { + /* set the profile property on the gconfaudiosink to "music and movies" */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (audio_sink), "profile")) + g_object_set (G_OBJECT (audio_sink), "profile", 1, NULL); + } + } else { + audio_sink = gst_element_factory_make ("fakesink", "audio-fake-sink"); + } + + if (type == BVW_USE_TYPE_VIDEO) { + if (width > 0 && width < SMALL_STREAM_WIDTH && + height > 0 && height < SMALL_STREAM_HEIGHT) { + bvw->priv->init_height = height; + bvw->priv->init_width = width; + GST_INFO ("forcing ximagesink, image size only %dx%d", width, height); + video_sink = gst_element_factory_make ("ximagesink", "video-sink"); + } else { + video_sink = gst_element_factory_make ("gconfvideosink", "video-sink"); + if (video_sink == NULL) { + g_warning ("Could not create element 'gconfvideosink'"); + /* Try to fallback on ximagesink */ + video_sink = gst_element_factory_make ("ximagesink", "video-sink"); + } + } +/* FIXME: April fool's day puzzle */ +#if 0 + if (video_sink) { + GDate d; + + g_date_clear (&d, 1); + g_date_set_time (&d, time (NULL)); + if (g_date_day (&d) == 1 && g_date_month (&d) == G_DATE_APRIL) { + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX"/puzzle_year", NULL); + + if (!confvalue || + gconf_value_get_int (confvalue) != g_date_year (&d)) { + GstElement *puzzle; + + gconf_client_set_int (bvw->priv->gc, GCONF_PREFIX"/puzzle_year", + g_date_year (&d), NULL); + + puzzle = gst_element_factory_make ("puzzle", NULL); + if (puzzle) { + GstElement *bin = gst_bin_new ("videosinkbin"); + GstPad *pad; + + gst_bin_add_many (GST_BIN (bin), puzzle, video_sink, NULL); + gst_element_link_pads (puzzle, "src", video_sink, "sink"); + pad = gst_element_get_pad (puzzle, "sink"); + gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + video_sink = bin; + } + } + + if (confvalue) + gconf_value_free (confvalue); + } + } +#endif + } else { + video_sink = gst_element_factory_make ("fakesink", "video-fake-sink"); + } + + if (video_sink) { + GstStateChangeReturn ret; + + /* need to set bus explicitly as it's not in a bin yet and + * poll_for_state_change() needs one to catch error messages */ + gst_element_set_bus (video_sink, bvw->priv->bus); + /* state change NULL => READY should always be synchronous */ + ret = gst_element_set_state (video_sink, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + /* Drop this video sink */ + gst_element_set_state (video_sink, GST_STATE_NULL); + gst_object_unref (video_sink); + /* Try again with ximagesink */ + video_sink = gst_element_factory_make ("ximagesink", "video-sink"); + gst_element_set_bus (video_sink, bvw->priv->bus); + ret = gst_element_set_state (video_sink, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + GstMessage *err_msg; + + err_msg = gst_bus_poll (bvw->priv->bus, GST_MESSAGE_ERROR, 0); + if (err_msg == NULL) { + g_warning ("Should have gotten an error message, please file a bug."); + g_set_error (err, BVW_ERROR, BVW_ERROR_VIDEO_PLUGIN, + _("Failed to open video output. It may not be available. " + "Please select another video output in the Multimedia " + "Systems Selector.")); + } else if (err) { + *err = bvw_error_from_gst_error (bvw, err_msg); + gst_message_unref (err_msg); + } + goto sink_error; + } + } + } else { + g_set_error (err, BVW_ERROR, BVW_ERROR_VIDEO_PLUGIN, + _("Could not find the video output. " + "You may need to install additional GStreamer plugins, " + "or select another video output in the Multimedia Systems " + "Selector.")); + goto sink_error; + } + + if (audio_sink) { + GstStateChangeReturn ret; + + /* need to set bus explicitly as it's not in a bin yet and + * poll_for_state_change() needs one to catch error messages */ + gst_element_set_bus (audio_sink, bvw->priv->bus); + + /* state change NULL => READY should always be synchronous */ + ret = gst_element_set_state (audio_sink, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + /* doesn't work, drop this audio sink */ + gst_element_set_state (audio_sink, GST_STATE_NULL); + gst_object_unref (audio_sink); + audio_sink = NULL; + /* Hopefully, fakesink should always work */ + if (type != BVW_USE_TYPE_AUDIO) + audio_sink = gst_element_factory_make ("fakesink", "audio-sink"); + if (audio_sink == NULL) { + GstMessage *err_msg; + + err_msg = gst_bus_poll (bvw->priv->bus, GST_MESSAGE_ERROR, 0); + if (err_msg == NULL) { + g_warning ("Should have gotten an error message, please file a bug."); + g_set_error (err, BVW_ERROR, BVW_ERROR_AUDIO_PLUGIN, + _("Failed to open audio output. You may not have " + "permission to open the sound device, or the sound " + "server may not be running. " + "Please select another audio output in the Multimedia " + "Systems Selector.")); + } else if (err) { + *err = bvw_error_from_gst_error (bvw, err_msg); + gst_message_unref (err_msg); + } + goto sink_error; + } + bvw->priv->uses_fakesink = TRUE; + } + } else { + g_set_error (err, BVW_ERROR, BVW_ERROR_AUDIO_PLUGIN, + _("Could not find the audio output. " + "You may need to install additional GStreamer plugins, or " + "select another audio output in the Multimedia Systems " + "Selector.")); + goto sink_error; + } + + do { + GstElement *bin; + GstPad *pad; + + bvw->priv->audio_capsfilter = + gst_element_factory_make ("capsfilter", "audiofilter"); + bin = gst_bin_new ("audiosinkbin"); + gst_bin_add_many (GST_BIN (bin), bvw->priv->audio_capsfilter, + audio_sink, NULL); + gst_element_link_pads (bvw->priv->audio_capsfilter, "src", + audio_sink, "sink"); + + pad = gst_element_get_pad (bvw->priv->audio_capsfilter, "sink"); + gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + + audio_sink = bin; + } while (0); + + /* now tell playbin */ + g_object_set (bvw->priv->play, "video-sink", video_sink, NULL); + g_object_set (bvw->priv->play, "audio-sink", audio_sink, NULL); + + bvw->priv->vis_plugins_list = NULL; + + g_signal_connect (bvw->priv->play, "notify::source", + G_CALLBACK (playbin_source_notify_cb), bvw); + g_signal_connect (bvw->priv->play, "notify::stream-info", + G_CALLBACK (playbin_stream_info_notify_cb), bvw); + + if (type == BVW_USE_TYPE_VIDEO) { + GstStateChangeReturn ret; + + /* wait for video sink to finish changing to READY state, + * otherwise we won't be able to detect the colorbalance interface */ + ret = gst_element_get_state (video_sink, NULL, NULL, 5 * GST_SECOND); + if (ret != GST_STATE_CHANGE_SUCCESS) { + GST_WARNING ("Timeout setting videosink to READY"); + g_set_error (err, BVW_ERROR, BVW_ERROR_VIDEO_PLUGIN, + _("Failed to open video output. It may not be available. " + "Please select another video output in the Multimedia Systems Selector.")); + return NULL; + } + bvw_update_interface_implementations (bvw); + } + + /* we want to catch "prepare-xwindow-id" element messages synchroneously */ + gst_bus_set_sync_handler (bvw->priv->bus, gst_bus_sync_signal_handler, bvw); + + bvw->priv->sig_bus_sync = + g_signal_connect (bvw->priv->bus, "sync-message::element", + G_CALLBACK (bvw_element_msg_sync), bvw); + + if (GST_IS_BIN (video_sink)) { + /* video sink bins like gconfvideosink might remove their children and + * create new ones when set to NULL state, and they are currently set + * to NULL state whenever playbin re-creates its internal video bin + * (it sets all elements to NULL state before gst_bin_remove()ing them) */ + g_signal_connect (video_sink, "element-added", + G_CALLBACK (got_new_video_sink_bin_element), bvw); + } + + /* audio out, if any */ + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX"/audio_output_type", NULL); + if (confvalue != NULL && + (type != BVW_USE_TYPE_METADATA && type != BVW_USE_TYPE_CAPTURE)) { + bvw->priv->speakersetup = gconf_value_get_int (confvalue); + bacon_video_widget_set_audio_out_type (bvw, bvw->priv->speakersetup); + gconf_value_free (confvalue); + } else if (type == BVW_USE_TYPE_METADATA || type == BVW_USE_TYPE_CAPTURE) { + bvw->priv->speakersetup = -1; + /* don't set up a filter for the speaker setup, anything is fine */ + } else { + bvw->priv->speakersetup = -1; + bacon_video_widget_set_audio_out_type (bvw, BVW_AUDIO_SOUND_STEREO); + } + + /* visualization */ + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/show_vfx", NULL); + if (confvalue != NULL) { + bvw->priv->show_vfx = gconf_value_get_bool (confvalue); + gconf_value_free (confvalue); + } + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/visual_quality", NULL); + if (confvalue != NULL) { + bvw->priv->visq = gconf_value_get_int (confvalue); + gconf_value_free (confvalue); + } +#if 0 + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/visual", NULL); + if (confvalue != NULL) { + bvw->priv->vis_element = + gst_element_factory_make (gconf_value_get_string (confvalue), NULL); + gconf_value_free (confvalue); + } + setup_vis (); +#endif + + /* tv/conn (not used yet) */ + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/tv_out_type", NULL); + if (confvalue != NULL) { + bvw->priv->tv_out_type = gconf_value_get_int (confvalue); + gconf_value_free (confvalue); + } + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/connection_speed", NULL); + if (confvalue != NULL) { + bacon_video_widget_set_connection_speed (bvw, + gconf_value_get_int (confvalue)); + gconf_value_free (confvalue); + } + + /* those are private to us, i.e. not Xine-compatible */ + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/buffer-size", NULL); + if (confvalue != NULL) { + g_object_set (bvw->priv->play, "queue-size", + (guint64) (GST_SECOND * gconf_value_get_float (confvalue)), NULL); + gconf_value_free (confvalue); + } + confvalue = gconf_client_get_without_default (bvw->priv->gc, + GCONF_PREFIX "/network-buffer-threshold", NULL); + if (confvalue != NULL) { + g_object_set (bvw->priv->play, "queue-threshold", + (guint64) (GST_SECOND * gconf_value_get_float (confvalue)), NULL); + gconf_value_free (confvalue); + } + + return GTK_WIDGET (bvw); + + /* errors */ +sink_error: + { + if (video_sink) { + gst_element_set_state (video_sink, GST_STATE_NULL); + gst_object_unref (video_sink); + } + if (audio_sink) { + gst_element_set_state (audio_sink, GST_STATE_NULL); + gst_object_unref (audio_sink); + } + + g_object_ref (bvw); + g_object_ref_sink (G_OBJECT (bvw)); + g_object_unref (bvw); + + return NULL; + } +} + diff --git a/trunk/src/backend/bacon-video-widget-xine.c b/trunk/src/backend/bacon-video-widget-xine.c new file mode 100644 index 000000000..a43d12077 --- /dev/null +++ b/trunk/src/backend/bacon-video-widget-xine.c @@ -0,0 +1,4058 @@ +/* + * Copyright (C) 2001-2005 the xine project + * Heavily modified by Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Id$ + * + * the xine engine in a widget - implementation + */ + +#include <config.h> + +#ifdef HAVE_NVTV +#include <nvtv_simple.h> +#endif + + +/* system */ +#include <math.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sched.h> +/* X11 */ +#include <X11/X.h> +#include <X11/Xlib.h> +/* gtk+/gnome */ +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <gconf/gconf-client.h> +#include <glib/gi18n.h> +/* xine */ +#define XINE_ENABLE_EXPERIMENTAL_FEATURES +#include <xine.h> + +#include "debug.h" +#include "bacon-video-widget.h" +#include "bacon-video-widget-common.h" +#include "baconvideowidget-marshal.h" +#include "video-utils.h" +#include "bacon-resize.h" + +#define DEFAULT_HEIGHT 180 +#define DEFAULT_WIDTH 240 +#define CONFIG_FILE ".gnome2"G_DIR_SEPARATOR_S"totem_config" + +/* Signals */ +enum { + ERROR, + EOS, + REDIRECT, + TITLE_CHANGE, + CHANNELS_CHANGE, + TICK, + GOT_METADATA, + BUFFERING, + LAST_SIGNAL +}; + +/* Enum for none-signal stuff that needs to go through the AsyncQueue */ +enum { + RATIO_ASYNC, + REDIRECT_ASYNC, + TITLE_CHANGE_ASYNC, + EOS_ASYNC, + CHANNELS_CHANGE_ASYNC, + BUFFERING_ASYNC, + MESSAGE_ASYNC, + ERROR_ASYNC +}; + +typedef struct { + int signal; + char *msg; + int num; + gboolean fatal; +} signal_data; + +/* Arguments */ +enum { + PROP_0, + PROP_LOGO_MODE, + PROP_SPEED, + PROP_POSITION, + PROP_CURRENT_TIME, + PROP_STREAM_LENGTH, + PROP_PLAYING, + PROP_SEEKABLE, + PROP_SHOWCURSOR, + PROP_MEDIADEV, + PROP_SHOW_VISUALS, + PROP_VOLUME +}; + +static int video_props[4] = { + XINE_PARAM_VO_BRIGHTNESS, + XINE_PARAM_VO_CONTRAST, + XINE_PARAM_VO_SATURATION, + XINE_PARAM_VO_HUE +}; + +static char *video_props_str[4] = { + GCONF_PREFIX"/brightness", + GCONF_PREFIX"/contrast", + GCONF_PREFIX"/saturation", + GCONF_PREFIX"/hue" +}; + +struct BaconVideoWidgetPrivate { + /* Xine stuff */ + xine_t *xine; + xine_stream_t *stream; + xine_video_port_t *vo_driver; + xine_audio_port_t *ao_driver; + gboolean ao_driver_none; + xine_event_queue_t *ev_queue; + double display_ratio; + + /* Configuration */ + GConfClient *gc; + BvwUseType type; + char *mediadev; + + /* X stuff */ + Display *display; + int screen; + GdkWindow *video_window; + GdkCursor *cursor; + + /* Opening thread for fd://0 */ + GThread *open_thread; + + /* Visual effects */ + char *vis_name; + gboolean show_vfx; + gboolean using_vfx; + xine_post_t *vis; + GList *visuals; + char *queued_vis; + VisualsQuality quality; + + /* Seeking stuff */ + int seeking; + float seek_dest; + gint64 seek_dest_time; + + /* Logo */ + gboolean logo_mode; + GdkPixbuf *logo_pixbuf; + /* Logo, save frame_output_cb members */ + int dest_x, dest_y, dest_width, dest_height, win_x, win_y; + double dest_pixel_aspect; + + /* Other stuff */ + int xpos, ypos; + gboolean can_dvd, can_vcd, can_cdda; + guint tick_id; + gboolean have_xvidmode; + gboolean auto_resize; + int volume; + BaconVideoWidgetAudioOutType audio_out_type; + TvOutType tvout; + gboolean is_live; + char *codecs_path; + gboolean got_redirect; + gboolean has_subtitle; + /* Whether the last button event was consumed internally */ + gboolean bevent_consumed; + + GAsyncQueue *queue; + int video_width, video_height; + int init_width, init_height; + + /* fullscreen stuff */ + gboolean fullscreen_mode; + gboolean cursor_shown; + int screenid; +}; + +static const char *mms_bandwidth_strs[] = { + "14.4 Kbps (Modem)", + "19.2 Kbps (Modem)", + "28.8 Kbps (Modem)", + "33.6 Kbps (Modem)", + "34.4 Kbps (Modem)", + "57.6 Kbps (Modem)", + "115.2 Kbps (ISDN)", + "262.2 Kbps (Cable/DSL)", + "393.2 Kbps (Cable/DSL)", + "524.3 Kbps (Cable/DSL)", + "1.5 Mbps (T1)", + "10.5 Mbps (LAN)", + NULL +}; + +static const char *audio_out_types_strs[] = { + "Mono 1.0", + "Stereo 2.0", + "Headphones 2.0", + "Stereo 2.1", + "Surround 3.0", + "Surround 4.0", + "Surround 4.1", + "Surround 5.0", + "Surround 5.1", + "Surround 6.0", + "Surround 6.1", + "Surround 7.1", + "Pass Through", + NULL +}; + +static const char *demux_strategies_str[] = { + "default", + "reverse", + "content", + "extension", + NULL +}; + +static void bacon_video_widget_class_init (BaconVideoWidgetClass *klass); +static void bacon_video_widget_init (BaconVideoWidget *bvw); + +static void setup_config (BaconVideoWidget *bvw); + +static void bacon_video_widget_set_property (GObject *object, + guint property_id, const GValue *value, GParamSpec *pspec); +static void bacon_video_widget_get_property (GObject *object, + guint property_id, GValue *value, GParamSpec *pspec); + +static void bacon_video_widget_realize (GtkWidget *widget); +static void bacon_video_widget_unrealize (GtkWidget *widget); +static void bacon_video_widget_finalize (GObject *object); + +static gboolean bacon_video_widget_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean bacon_video_widget_motion_notify (GtkWidget *widget, + GdkEventMotion *event); +static gboolean bacon_video_widget_button_press (GtkWidget *widget, + GdkEventButton *event); +static void bacon_video_widget_show (GtkWidget *widget); +static void bacon_video_widget_hide (GtkWidget *widget); + +static void bacon_video_widget_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void bacon_video_widget_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static xine_video_port_t * load_video_out_driver (BaconVideoWidget *bvw, + BvwUseType type); +static xine_audio_port_t * load_audio_out_driver (BaconVideoWidget *bvw, + gboolean null_out, GError **error); +static gboolean bacon_video_widget_tick_send (BaconVideoWidget *bvw); +static void bacon_video_widget_set_visuals_quality_size (BaconVideoWidget *bvw, + int h, int w, int fps); + +static GtkWidgetClass *parent_class = NULL; + +static void xine_event (void *user_data, const xine_event_t *event); +static gboolean bacon_video_widget_idle_signal (BaconVideoWidget *bvw); +static void show_vfx_update (BaconVideoWidget *bvw, gboolean show_visuals); + +static int bvw_table_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(BaconVideoWidget, bacon_video_widget, GTK_TYPE_BOX) + +static void +bacon_video_widget_class_init (BaconVideoWidgetClass *klass) +{ + + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + + parent_class = gtk_type_class (gtk_box_get_type ()); + + /* GtkWidget */ + widget_class->realize = bacon_video_widget_realize; + widget_class->unrealize = bacon_video_widget_unrealize; + widget_class->size_request = bacon_video_widget_size_request; + widget_class->size_allocate = bacon_video_widget_size_allocate; + widget_class->expose_event = bacon_video_widget_expose; + widget_class->motion_notify_event = bacon_video_widget_motion_notify; + widget_class->button_press_event = bacon_video_widget_button_press; + widget_class->show = bacon_video_widget_show; + widget_class->hide = bacon_video_widget_hide; + + /* GObject */ + object_class->set_property = bacon_video_widget_set_property; + object_class->get_property = bacon_video_widget_get_property; + object_class->finalize = bacon_video_widget_finalize; + + /* Properties */ + g_object_class_install_property (object_class, PROP_LOGO_MODE, + g_param_spec_boolean ("logo_mode", NULL, NULL, + FALSE, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_POSITION, + g_param_spec_int64 ("position", NULL, NULL, + 0, G_MAXINT64, 0, G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_STREAM_LENGTH, + g_param_spec_int64 ("stream_length", NULL, NULL, + 0, G_MAXINT64, 0, G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_PLAYING, + g_param_spec_boolean ("playing", NULL, NULL, + FALSE, G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_SEEKABLE, + g_param_spec_boolean ("seekable", NULL, NULL, + FALSE, G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_VOLUME, + g_param_spec_int ("volume", NULL, NULL, + 0, 100, 0, G_PARAM_READABLE)); + g_object_class_install_property (object_class, PROP_SHOWCURSOR, + g_param_spec_boolean ("showcursor", NULL, NULL, + FALSE, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_MEDIADEV, + g_param_spec_string ("mediadev", NULL, NULL, + FALSE, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_SHOW_VISUALS, + g_param_spec_boolean ("showvisuals", NULL, NULL, + FALSE, G_PARAM_WRITABLE)); + + /* Signals */ + bvw_table_signals[ERROR] = + g_signal_new ("error", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, error), + NULL, NULL, + baconvideowidget_marshal_VOID__STRING_BOOLEAN_BOOLEAN, + G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + + bvw_table_signals[EOS] = + g_signal_new ("eos", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, eos), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + bvw_table_signals[REDIRECT] = + g_signal_new ("got-redirect", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, got_redirect), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + bvw_table_signals[GOT_METADATA] = + g_signal_new ("got-metadata", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, got_metadata), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + bvw_table_signals[TITLE_CHANGE] = + g_signal_new ("title-change", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, title_change), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + bvw_table_signals[CHANNELS_CHANGE] = + g_signal_new ("channels-change", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, channels_change), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + bvw_table_signals[TICK] = + g_signal_new ("tick", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, tick), + NULL, NULL, + baconvideowidget_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN, + G_TYPE_NONE, 4, G_TYPE_INT64, G_TYPE_INT64, + G_TYPE_FLOAT, G_TYPE_BOOLEAN); + + bvw_table_signals[BUFFERING] = + g_signal_new ("buffering", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVideoWidgetClass, buffering), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); +} + +static void +bacon_video_widget_init (BaconVideoWidget *bvw) +{ + const char *const *autoplug_list; + int i = 0; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (bvw), GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET (bvw), GTK_DOUBLE_BUFFERED); + + bvw->com = g_new0 (BaconVideoWidgetCommon, 1); + bvw->priv = g_new0 (BaconVideoWidgetPrivate, 1); + bvw->priv->xine = xine_new (); + bvw->priv->cursor_shown = TRUE; + bvw->priv->volume = 0; + + bvw->priv->init_width = 0; + bvw->priv->init_height = 0; + + bvw->priv->queue = g_async_queue_new (); + + /* init configuration */ + bvw->priv->gc = gconf_client_get_default (); + setup_config (bvw); + + xine_init (bvw->priv->xine); + + /* Can we play DVDs and VCDs ? */ + autoplug_list = xine_get_autoplay_input_plugin_ids (bvw->priv->xine); + while (autoplug_list && autoplug_list[i]) + { + if (g_ascii_strcasecmp (autoplug_list[i], "VCD") == 0) + bvw->priv->can_vcd = TRUE; + else if (g_ascii_strcasecmp (autoplug_list[i], "VCDO") == 0) + bvw->priv->can_vcd = TRUE; + else if (g_ascii_strcasecmp (autoplug_list[i], "DVD") == 0) + bvw->priv->can_dvd = TRUE; + else if (g_ascii_strcasecmp (autoplug_list[i], "CD") == 0) + bvw->priv->can_cdda = TRUE; + i++; + } + + bvw->priv->tick_id = g_timeout_add (140, + (GSourceFunc) bacon_video_widget_tick_send, bvw); +} + +static void +bacon_video_widget_finalize (GObject *object) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) object; + + if (bvw->priv->gc) + g_object_unref (G_OBJECT (bvw->priv->gc)); + + if (bvw->priv->xine != NULL) { + xine_plugins_garbage_collector (bvw->priv->xine); + xine_exit (bvw->priv->xine); + bvw->priv->xine = NULL; + } + if (bvw->priv->cursor != NULL) { + gdk_cursor_unref (bvw->priv->cursor); + bvw->priv->cursor = NULL; + } + if (bvw->priv->logo_pixbuf != NULL) { + gdk_pixbuf_unref (bvw->priv->logo_pixbuf); + bvw->priv->logo_pixbuf = NULL; + } + g_free (bvw->priv->vis_name); + g_free (bvw->priv->mediadev); + g_object_unref (G_OBJECT (bvw->priv->gc)); + g_free (bvw->priv->codecs_path); + + g_list_foreach (bvw->priv->visuals, (GFunc) g_free, NULL); + g_list_free (bvw->priv->visuals); + + g_idle_remove_by_data (bvw); + g_async_queue_unref (bvw->priv->queue); + G_OBJECT_CLASS (parent_class)->finalize (object); + + g_free (bvw->priv); + g_free (bvw->com); +} + +static void +dest_size_cb (void *data, + int video_width, int video_height, + double video_pixel_aspect, + int *dest_width, int *dest_height, + double *dest_pixel_aspect) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *)data; + + /* correct size with video_pixel_aspect */ + if (video_pixel_aspect >= bvw->priv->display_ratio) + video_width = video_width * video_pixel_aspect + / bvw->priv->display_ratio + .5; + else + video_height = video_height * bvw->priv->display_ratio + / video_pixel_aspect + .5; + + *dest_width = GTK_WIDGET(bvw)->allocation.width; + *dest_height = GTK_WIDGET(bvw)->allocation.height; + *dest_pixel_aspect = bvw->priv->display_ratio; +} + +static void +frame_output_cb (void *bvw_gen, + int video_width, int video_height, + double video_pixel_aspect, + int *dest_x, int *dest_y, + int *dest_width, int *dest_height, + double *dest_pixel_aspect, + int *win_x, int *win_y) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) bvw_gen; + + if (bvw == NULL || bvw->priv == NULL) + return; + if (bvw->priv->logo_mode != FALSE) { + *dest_x = bvw->priv->dest_x; + *dest_y = bvw->priv->dest_y; + *dest_width = bvw->priv->dest_width; + *dest_height = bvw->priv->dest_height; + *win_x = bvw->priv->win_x; + *win_y = bvw->priv->win_y; + *dest_pixel_aspect = bvw->priv->dest_pixel_aspect; + return; + } + + /* correct size with video_pixel_aspect */ + if (video_pixel_aspect >= bvw->priv->display_ratio) + { + video_width = video_width * video_pixel_aspect + / bvw->priv->display_ratio + .5; + } else { + video_height = video_height * bvw->priv->display_ratio + / video_pixel_aspect + .5; + } + + *dest_x = 0; + *dest_y = 0; + *win_x = bvw->priv->xpos; + *win_y = bvw->priv->ypos; + + *dest_width = GTK_WIDGET(bvw)->allocation.width; + *dest_height = GTK_WIDGET(bvw)->allocation.height; + + /* Size changed */ + if (bvw->priv->video_width != video_width + || bvw->priv->video_height != video_height) + { + bvw->priv->video_width = video_width; + bvw->priv->video_height = video_height; + + if (bvw->priv->auto_resize != FALSE + && bvw->priv->logo_mode == FALSE + && bvw->priv->fullscreen_mode == FALSE) + { + signal_data *data; + + data = g_new0 (signal_data, 1); + data->signal = RATIO_ASYNC; + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) + bacon_video_widget_idle_signal, bvw); + } + } + + *dest_pixel_aspect = bvw->priv->display_ratio; + + /* Save them */ + bvw->priv->dest_x = *dest_x; + bvw->priv->dest_y = *dest_y; + bvw->priv->dest_width = *dest_width; + bvw->priv->dest_height = *dest_height; + bvw->priv->win_x = *win_x; + bvw->priv->win_y = *win_y; + bvw->priv->dest_pixel_aspect = *dest_pixel_aspect; +} + +static xine_video_port_t * +load_video_out_driver (BaconVideoWidget *bvw, BvwUseType type) +{ + double res_h, res_v; + x11_visual_t vis; + const char *video_driver_id; + xine_video_port_t *vo_driver; + static char *drivers[] = { "xv", "xshm" }; + guint i; + + if (type == BVW_USE_TYPE_METADATA) { + return xine_open_video_driver (bvw->priv->xine, + "none", XINE_VISUAL_TYPE_NONE, NULL); + } else if (type == BVW_USE_TYPE_CAPTURE) { + return xine_new_framegrab_video_port (bvw->priv->xine); + } + + vis.display = bvw->priv->display; + vis.screen = bvw->priv->screen; + vis.d = GDK_WINDOW_XID (bvw->priv->video_window); + res_h = (DisplayWidth (bvw->priv->display, bvw->priv->screen) * 1000 / + DisplayWidthMM (bvw->priv->display, + bvw->priv->screen)); + res_v = (DisplayHeight (bvw->priv->display, bvw->priv->screen) * 1000 / + DisplayHeightMM (bvw->priv->display, + bvw->priv->screen)); + bvw->priv->display_ratio = res_v / res_h; + + if (fabs (bvw->priv->display_ratio - 1.0) < 0.01) { + bvw->priv->display_ratio = 1.0; + } + + vis.dest_size_cb = dest_size_cb; + vis.frame_output_cb = frame_output_cb; + vis.user_data = bvw; + + /* Try to init video with stored information */ + video_driver_id = xine_config_register_string (bvw->priv->xine, + "video.driver", "auto", "video driver to use", + NULL, 10, NULL, NULL); + + /* Don't try to load anything but the xshm plugin if we're not + * on a local display */ + if (totem_display_is_local () == FALSE || + (bvw->priv->init_width < SMALL_STREAM_WIDTH + && bvw->priv->init_width > 0 + && bvw->priv->init_height < SMALL_STREAM_HEIGHT + && bvw->priv->init_height > 0)) + { + return xine_open_video_driver (bvw->priv->xine, "xshm", + XINE_VISUAL_TYPE_X11, (void *) &vis); + } + + if (strcmp (video_driver_id, "auto") != 0) + { + vo_driver = xine_open_video_driver (bvw->priv->xine, + video_driver_id, + XINE_VISUAL_TYPE_X11, + (void *) &vis); + if (vo_driver) + return vo_driver; + } + + /* The types are hardcoded for now */ + for (i = 0; i < G_N_ELEMENTS (drivers); i++) { + vo_driver = xine_open_video_driver (bvw->priv->xine, + drivers[i], XINE_VISUAL_TYPE_X11, + (void *) &vis); + if (vo_driver) + break; + } + + return vo_driver; +} + +static xine_audio_port_t * +load_audio_out_driver (BaconVideoWidget *bvw, gboolean null_out, + GError **error) +{ + xine_audio_port_t *ao_driver; + const char *audio_driver_id; + + if (null_out != FALSE) { + ao_driver = xine_open_audio_driver (bvw->priv->xine, + "none", NULL); + if (ao_driver != NULL) { + bvw->priv->ao_driver_none = TRUE; + } + return ao_driver; + } + + audio_driver_id = xine_config_register_string (bvw->priv->xine, + "audio.driver", "auto", "audio driver to use", + NULL, 10, NULL, NULL); + + /* No configuration, fallback to auto */ + if (audio_driver_id == NULL || strcmp (audio_driver_id, "") == 0) + audio_driver_id = g_strdup ("auto"); + + /* We know how to handle null driver */ + if (strcmp (audio_driver_id, "null") == 0) + return NULL; + + /* auto probe */ + if (strcmp (audio_driver_id, "auto") == 0) + ao_driver = xine_open_audio_driver (bvw->priv->xine, + NULL, NULL); + else + ao_driver = xine_open_audio_driver (bvw->priv->xine, + audio_driver_id, NULL); + + /* if it failed without autoprobe, probe */ + if (ao_driver == NULL && strcmp (audio_driver_id, "auto") != 0) + ao_driver = xine_open_audio_driver (bvw->priv->xine, + NULL, NULL); + + if (ao_driver == NULL && strcmp (audio_driver_id, "auto") != 0) + { + g_set_error (error, BVW_ERROR, BVW_ERROR_AUDIO_PLUGIN, + _("Couldn't load the '%s' audio driver\n" + "Check that the device is not busy."), + audio_driver_id ? audio_driver_id : "auto" ); + return NULL; + } + + return ao_driver; +} + +static void +bvw_config_helper_string (xine_t *xine, const char *id, const char *val, + xine_cfg_entry_t *entry) +{ + memset (entry, 0, sizeof (entry)); + + if (!xine_config_lookup_entry (xine, id, entry)) + { + xine_config_register_string (xine, id, val, "", NULL, 10, + NULL, NULL); + xine_config_lookup_entry (xine, id, entry); + } +} + +static void +bvw_config_helper_num (xine_t *xine, const char *id, int val, + xine_cfg_entry_t *entry) +{ + memset (entry, 0, sizeof (entry)); + + if (!xine_config_lookup_entry (xine, id, entry)) + { + xine_config_register_num (xine, id, val, 0, NULL, 10, + NULL, NULL); + xine_config_lookup_entry (xine, id, entry); + } +} + +static void +setup_config (BaconVideoWidget *bvw) +{ + char *path; + xine_cfg_entry_t entry; + + path = g_build_path (G_DIR_SEPARATOR_S, + g_get_home_dir (), CONFIG_FILE, NULL); + xine_config_load (bvw->priv->xine, path); + g_free (path); + + /* default demux strategy */ + xine_config_register_enum (bvw->priv->xine, + "engine.demux.strategy", + 0, + (char **) demux_strategies_str, + "media format detection strategy", + NULL, 10, NULL, NULL); + + xine_config_lookup_entry (bvw->priv->xine, + "engine.demux.strategy", &entry); + entry.num_value = 0; + xine_config_update_entry (bvw->priv->xine, &entry); + + if (bvw->priv->gc == NULL) + return; + + /* Disable CDDB, we'll use Musicbrainz instead */ + bvw_config_helper_num (bvw->priv->xine, + "media.audio_cd.use_cddb", 1, &entry); + entry.num_value = 0; + xine_config_update_entry (bvw->priv->xine, &entry); + + if (bvw->priv->gc == NULL) { + g_warning ("GConf not available, broken installation?"); + return; + } + + /* Debug configuration */ + if (gconf_client_get_bool (bvw->priv->gc, + GCONF_PREFIX"/debug", NULL) == FALSE) + { + xine_engine_set_param (bvw->priv->xine, + XINE_ENGINE_PARAM_VERBOSITY, + XINE_VERBOSITY_NONE); + } else { + xine_engine_set_param (bvw->priv->xine, + XINE_ENGINE_PARAM_VERBOSITY, + 0xff); + } + + /* Proxy configuration */ + if (gconf_client_get_bool (bvw->priv->gc, "/system/http_proxy/use_http_proxy", NULL) == FALSE) + { + bvw_config_helper_string (bvw->priv->xine, + "media.network.http_proxy_host", "", &entry); + entry.str_value = ""; + xine_config_update_entry (bvw->priv->xine, &entry); + + return; + } + + bvw_config_helper_string (bvw->priv->xine, + "media.network.http_proxy_host", "", &entry); + entry.str_value = gconf_client_get_string (bvw->priv->gc, + "/system/http_proxy/host", NULL); + xine_config_update_entry (bvw->priv->xine, &entry); + g_free (entry.str_value); + + bvw_config_helper_num (bvw->priv->xine, + "media.network.http_proxy_port", 8080, &entry); + entry.num_value = gconf_client_get_int (bvw->priv->gc, + "/system/http_proxy/port", NULL); + xine_config_update_entry (bvw->priv->xine, &entry); + + if (gconf_client_get_bool (bvw->priv->gc, "/system/http_proxy/use_authentication", NULL) == FALSE) + { + bvw_config_helper_string (bvw->priv->xine, + "media.network.http_proxy_user", g_get_user_name(), + &entry); + entry.str_value = ""; + xine_config_update_entry (bvw->priv->xine, &entry); + + bvw_config_helper_string (bvw->priv->xine, + "media.network.http_proxy_password", "", + &entry); + entry.str_value = ""; + xine_config_update_entry (bvw->priv->xine, &entry); + } else { + bvw_config_helper_string (bvw->priv->xine, + "media.network.http_proxy_user", g_get_user_name(), + &entry); + entry.str_value = gconf_client_get_string (bvw->priv->gc, + "/system/http_proxy/authentication_user", + NULL); + xine_config_update_entry (bvw->priv->xine, &entry); + g_free (entry.str_value); + + bvw_config_helper_string (bvw->priv->xine, + "media.network.http_proxy_password", "", + &entry); + entry.str_value = gconf_client_get_string (bvw->priv->gc, + "/system/http_proxy/authentication_password", + NULL); + xine_config_update_entry (bvw->priv->xine, &entry); + g_free (entry.str_value); + } +} + +static void +setup_config_stream (BaconVideoWidget *bvw) +{ + GConfValue *confvalue; + int value, i, tmp; + + if (bvw->priv->gc == NULL) + return; + + /* Setup brightness and contrast */ + if (bvw->priv->vo_driver == NULL) + return; + + for (i = 0; i < 4; i++) + { + confvalue = gconf_client_get_without_default (bvw->priv->gc, + video_props_str[i], NULL); + + if (confvalue != NULL) + { + value = gconf_value_get_int (confvalue); + gconf_value_free (confvalue); + } else { + value = 65535 / 2; + } + + tmp = xine_get_param (bvw->priv->stream, video_props[i]); + if (value != tmp) + { + xine_set_param (bvw->priv->stream, + video_props[i], value); + } + } +} + +static gboolean +bacon_video_widget_plugin_exists (BaconVideoWidget *bvw, const char *filename) +{ + char *path; + gboolean res; + + path = g_build_filename (bvw->priv->codecs_path, filename, NULL); + res = g_file_test (path, G_FILE_TEST_IS_REGULAR); + g_free (path); + return res; +} + +static gboolean +video_window_translate_point (BaconVideoWidget *bvw, int gui_x, int gui_y, + int *video_x, int *video_y) +{ + int res; + x11_rectangle_t rect; + + rect.x = gui_x; + rect.y = gui_y; + rect.w = 0; + rect.h = 0; + + res = xine_port_send_gui_data (bvw->priv->vo_driver, + XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO, (void*)&rect); + + if (res != -1) + { + /* the driver implements gui->video coordinate space + * translation so we use it */ + *video_x = rect.x; + *video_y = rect.y; + return TRUE; + } + + return FALSE; +} + +/* Changes the way xine skips while playing a DVD, + * 1 == CHAPTER + * 2 == TITLE + */ +static void +dvd_skip_behaviour (BaconVideoWidget *bvw, int behaviour) +{ + if (behaviour < 1 || behaviour > 2) + return; + + xine_config_register_num (bvw->priv->xine, + "media.dvd.skip_behaviour", + behaviour, + "DVD Skip behaviour", + NULL, + 10, + NULL, NULL); + + return; +} + +void +bacon_video_widget_dvd_event (BaconVideoWidget *bvw, BaconVideoWidgetDVDEvent type) +{ + xine_event_t event; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + switch (type) + { + case BVW_DVD_ROOT_MENU: + event.type = XINE_EVENT_INPUT_MENU1; + break; + case BVW_DVD_TITLE_MENU: + event.type = XINE_EVENT_INPUT_MENU2; + break; + case BVW_DVD_SUBPICTURE_MENU: + event.type = XINE_EVENT_INPUT_MENU4; + break; + case BVW_DVD_AUDIO_MENU: + event.type = XINE_EVENT_INPUT_MENU5; + break; + case BVW_DVD_ANGLE_MENU: + event.type = XINE_EVENT_INPUT_MENU6; + break; + case BVW_DVD_CHAPTER_MENU: + event.type = XINE_EVENT_INPUT_MENU7; + break; + case BVW_DVD_NEXT_CHAPTER: + dvd_skip_behaviour (bvw, 1); + event.type = XINE_EVENT_INPUT_NEXT; + break; + case BVW_DVD_PREV_CHAPTER: + dvd_skip_behaviour (bvw, 1); + event.type = XINE_EVENT_INPUT_PREVIOUS; + break; + case BVW_DVD_NEXT_TITLE: + dvd_skip_behaviour (bvw, 2); + event.type = XINE_EVENT_INPUT_NEXT; + break; + case BVW_DVD_PREV_TITLE: + dvd_skip_behaviour (bvw, 2); + event.type = XINE_EVENT_INPUT_PREVIOUS; + break; + case BVW_DVD_NEXT_ANGLE: + event.type = XINE_EVENT_INPUT_ANGLE_NEXT; + break; + case BVW_DVD_PREV_ANGLE: + event.type = XINE_EVENT_INPUT_ANGLE_PREVIOUS; + break; + case BVW_DVD_ROOT_MENU_UP: + event.type = XINE_EVENT_INPUT_UP; + break; + case BVW_DVD_ROOT_MENU_DOWN: + event.type = XINE_EVENT_INPUT_DOWN; + break; + case BVW_DVD_ROOT_MENU_LEFT: + event.type = XINE_EVENT_INPUT_LEFT; + break; + case BVW_DVD_ROOT_MENU_RIGHT: + event.type = XINE_EVENT_INPUT_RIGHT; + break; + case BVW_DVD_ROOT_MENU_SELECT: + event.type = XINE_EVENT_INPUT_SELECT; + break; + default: + return; + } + + event.stream = bvw->priv->stream; + event.data = NULL; + event.data_length = 0; + + xine_event_send (bvw->priv->stream, + (xine_event_t *) (&event)); +} + +static gboolean +generate_mouse_event (BaconVideoWidget *bvw, GdkEvent *event, + gboolean is_motion) +{ + GdkEventMotion *mevent = (GdkEventMotion *) event; + GdkEventButton *bevent = (GdkEventButton *) event; + int x, y; + gboolean retval; + + /* Don't even try to generate an event if we're dying */ + if (bvw->priv->stream == NULL) + return FALSE; + + if (is_motion == FALSE && bevent->button != 1) + return FALSE; + + if (is_motion != FALSE) + retval = video_window_translate_point (bvw, + mevent->x, mevent->y, &x, &y); + else + retval = video_window_translate_point (bvw, + bevent->x, bevent->y, &x, &y); + + if (retval != FALSE) + { + xine_event_t event; + xine_input_data_t input; + + if (is_motion != FALSE) + { + event.type = XINE_EVENT_INPUT_MOUSE_MOVE; + input.button = 0; /* Just motion. */ + } else { + event.type = XINE_EVENT_INPUT_MOUSE_BUTTON; + input.button = 1; + } + + input.x = x; + input.y = y; + event.stream = bvw->priv->stream; + event.data = &input; + event.data_length = sizeof(input); + + xine_event_send (bvw->priv->stream, + (xine_event_t *) (&event)); + + return TRUE; + } + + return FALSE; +} + +static gboolean +configure_cb (GtkWidget *widget, GdkEventConfigure *event, + BaconVideoWidget *bvw) +{ + bvw->priv->xpos = event->x + GTK_WIDGET (bvw)->allocation.x; + bvw->priv->ypos = event->y + GTK_WIDGET (bvw)->allocation.y; + + return FALSE; +} + +static void +size_changed_cb (GdkScreen *screen, BaconVideoWidget *bvw) +{ + double res_h, res_v, vis_width; + int vis_height, fps; + + res_h = gdk_screen_get_width (screen) * 1000 / + gdk_screen_get_width_mm (screen); + res_v = gdk_screen_get_height (screen) * 1000 / + gdk_screen_get_height_mm (screen); + + if (bacon_video_widget_common_get_vis_quality (bvw->priv->quality, &vis_height, &fps) == FALSE) + return; + + vis_width = vis_height * gdk_screen_get_width (screen) / + gdk_screen_get_height (screen); + + bvw->priv->display_ratio = res_v / res_h; + + if (fabs (bvw->priv->display_ratio - 1.0) < 0.01) { + bvw->priv->display_ratio = 1.0; + } + + bacon_video_widget_set_visuals_quality_size (bvw, + vis_width, vis_height, fps); +} + +static void +bacon_video_widget_realize (GtkWidget *widget) +{ + GdkWindowAttr attr; + BaconVideoWidget *bvw; + + bvw = BACON_VIDEO_WIDGET (widget); + if (bvw->priv->type != BVW_USE_TYPE_VIDEO) + { + g_warning ("Use type isn't video but we realized the widget"); + return; + } + + /* set realized flag */ + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + /* Create the widget's window */ + attr.x = widget->allocation.x; + attr.y = widget->allocation.y; + attr.width = widget->allocation.width; + attr.height = widget->allocation.height; + attr.window_type = GDK_WINDOW_CHILD; + attr.wclass = GDK_INPUT_OUTPUT; + attr.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK; + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attr, GDK_WA_X | GDK_WA_Y); + gdk_window_show (widget->window); + + /* Flush, so that the window is really shown */ + gdk_flush (); + gdk_window_set_user_data (widget->window, bvw); + + bvw->priv->video_window = widget->window; + + widget->style = gtk_style_attach (widget->style, widget->window); + + /* Set a black background */ + gdk_draw_rectangle (widget->window, widget->style->black_gc, TRUE, + attr.x, attr.y, + attr.width, attr.height); + + /* track configure events of toplevel window */ + g_signal_connect (G_OBJECT (gtk_widget_get_toplevel (widget)), + "configure-event", + G_CALLBACK (configure_cb), bvw); + + /* get screen size changes */ + g_signal_connect (G_OBJECT (gtk_widget_get_screen (widget)), + "size-changed", G_CALLBACK (size_changed_cb), bvw); + + /* Now onto the video out driver */ + bvw->priv->display = XOpenDisplay (gdk_display_get_name + (gdk_display_get_default ())); + bvw->priv->screen = DefaultScreen (bvw->priv->display); + + bvw->priv->vo_driver = load_video_out_driver (bvw, bvw->priv->type); + + if (bvw->priv->vo_driver == NULL) + { + signal_data *sigdata; + + bvw->priv->vo_driver = load_video_out_driver + (bvw, BVW_USE_TYPE_METADATA); + + /* We need to use an async signal, otherwise we try to + * unrealize the widget before it's finished realizing */ + sigdata = g_new0 (signal_data, 1); + sigdata->signal = ERROR_ASYNC; + sigdata->msg = _("No video output is available. Make sure that the program is correctly installed."); + sigdata->fatal = TRUE; + g_async_queue_push (bvw->priv->queue, sigdata); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + } + + bvw->priv->ao_driver = load_audio_out_driver (bvw, FALSE, NULL); + + if (bvw->priv->ao_driver != NULL + && bvw->priv->ao_driver_none == FALSE) + { + BaconVideoWidgetAudioOutType type; + + if (bvw->priv->vis_name == NULL) + bvw->priv->vis_name = g_strdup ("goom"); + type = bacon_video_widget_get_audio_out_type (bvw); + bacon_video_widget_set_audio_out_type (bvw, type); + } else { + g_free (bvw->priv->vis_name); + bvw->priv->vis_name = NULL; + } + + bvw->priv->have_xvidmode = bacon_resize_init (); + bvw->priv->stream = xine_stream_new (bvw->priv->xine, + bvw->priv->ao_driver, bvw->priv->vo_driver); + setup_config_stream (bvw); + bvw->priv->ev_queue = xine_event_new_queue (bvw->priv->stream); + + /* Setup xine events */ + xine_event_create_listener_thread (bvw->priv->ev_queue, + xine_event, (void *) bvw); + +#ifdef HAVE_NVTV + if (!(nvtv_simple_init() && nvtv_enable_autoresize(TRUE))) { + nvtv_simple_enable(FALSE); + } +#endif + + return; +} + +static gboolean +bacon_video_widget_idle_signal (BaconVideoWidget *bvw) +{ + int queue_length; + signal_data *data; + + data = g_async_queue_try_pop (bvw->priv->queue); + if (data == NULL) + return FALSE; + + TE (); + + switch (data->signal) + { + case RATIO_ASYNC: + bacon_video_widget_set_scale_ratio (bvw, 1); + break; + case REDIRECT_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[REDIRECT], + 0, data->msg); + break; + case TITLE_CHANGE_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[TITLE_CHANGE], + 0, data->msg); + break; + case EOS_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[EOS], 0, NULL); + break; + case CHANNELS_CHANGE_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[CHANNELS_CHANGE], 0, NULL); + break; + case BUFFERING_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[BUFFERING], + 0, data->num); + break; + case MESSAGE_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[ERROR], + 0, data->msg, TRUE, FALSE); + break; + case ERROR_ASYNC: + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[ERROR], 0, + data->msg, TRUE, data->fatal); + break; + default: + g_assert_not_reached (); + } + + TL (); + + g_free (data->msg); + g_free (data); + queue_length = g_async_queue_length (bvw->priv->queue); + + return (queue_length > 0); +} + +static void +xine_event_message (BaconVideoWidget *bvw, xine_ui_message_data_t *data) +{ + char *message; + int num; + signal_data *sigdata; + const char *params; + + message = NULL; + + switch (data->type) + { + case XINE_MSG_NO_ERROR: + return; + case XINE_MSG_GENERAL_WARNING: + return; + case XINE_MSG_UNKNOWN_HOST: + num = BVW_ERROR_UNKNOWN_HOST; + message = g_strdup (_("The server you are trying to connect to is not known.")); + break; + case XINE_MSG_UNKNOWN_DEVICE: + num = BVW_ERROR_INVALID_DEVICE; + message = g_strdup_printf (_("The device name you specified (%s) seems to be invalid."), (char *) data + data->parameters); + break; + case XINE_MSG_NETWORK_UNREACHABLE: + num = BVW_ERROR_NETWORK_UNREACHABLE; + message = g_strdup_printf (_("The server you are trying to connect to (%s) is unreachable."), (char *) data + data->parameters); + break; + case XINE_MSG_CONNECTION_REFUSED: + num = BVW_ERROR_CONNECTION_REFUSED; + message = g_strdup (_("The connection to this server was refused.")); + break; + case XINE_MSG_FILE_NOT_FOUND: + num = BVW_ERROR_FILE_NOT_FOUND; + message = g_strdup (_("The specified movie could not be found.")); + break; + case XINE_MSG_READ_ERROR: + if (g_str_has_prefix (bvw->com->mrl, "dvd:") != FALSE) + { + num = BVW_ERROR_DVD_ENCRYPTED; + message = g_strdup (_("The source seems encrypted, and can't be read. Are you trying to play an encrypted DVD without libdvdcss?")); + } else { + num = BVW_ERROR_READ_ERROR; + message = g_strdup (_("The movie could not be read.")); + } + break; + case XINE_MSG_LIBRARY_LOAD_ERROR: + params = (char *) data + data->parameters; + if (bacon_video_widget_plugin_exists (bvw, params) == FALSE) + return; + /* Only if the file could really not be loaded */ + num = BVW_ERROR_PLUGIN_LOAD; + message = g_strdup_printf (_("A problem occurred while loading a library or a decoder (%s)."), params); + break; + case XINE_MSG_ENCRYPTED_SOURCE: + if (g_str_has_prefix (bvw->com->mrl, "dvd:") != FALSE) + { + num = BVW_ERROR_DVD_ENCRYPTED; + message = g_strdup (_("The source seems encrypted, and can't be read. Are you trying to play an encrypted DVD without libdvdcss?")); + } else { + num = BVW_ERROR_FILE_ENCRYPTED; + message = g_strdup (_("This file is encrypted and cannot be played back.")); + } + break; + case XINE_MSG_SECURITY: + num = BVW_ERROR_GENERIC; + message = g_strdup (_("For security reasons, this movie can not be played back.")); + break; + case XINE_MSG_AUDIO_OUT_UNAVAILABLE: + xine_stop (bvw->priv->stream); + num = BVW_ERROR_AUDIO_BUSY; + message = g_strdup (_("The audio device is busy. Is another application using it?")); + break; + case XINE_MSG_PERMISSION_ERROR: + num = BVW_ERROR_FILE_PERMISSION; + if (g_str_has_prefix (bvw->com->mrl, "file:") != FALSE) + message = g_strdup (_("You are not allowed to open this file.")); + else + message = g_strdup (_("The server refused access to this file or stream.")); + break; + +/* FIXME missing stuff from libxine includes (<= 1.1.x) */ +#ifndef XINE_MSG_FILE_EMPTY +#define XINE_MSG_FILE_EMPTY 13 +#endif + case XINE_MSG_FILE_EMPTY: + num = BVW_ERROR_EMPTY_FILE; + message = g_strdup (_("The file you tried to play is an empty file.")); + break; + default: + D("xine_event_message: unhandled error\ntype: %d", data->type); + return; + } + + sigdata = g_new0 (signal_data, 1); + sigdata->signal = ERROR_ASYNC; + sigdata->msg = message; + sigdata->num = num; + g_async_queue_push (bvw->priv->queue, sigdata); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); +} + +static void +xine_event (void *user_data, const xine_event_t *event) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) user_data; + xine_ui_data_t *ui_data; + xine_progress_data_t *prg; + xine_mrl_reference_data_t *ref; + xine_spu_button_t *spubtn; + signal_data *data; + + switch (event->type) + { + case XINE_EVENT_SPU_BUTTON: + spubtn = (xine_spu_button_t *) event->data; + if (spubtn->direction) + { + if (bvw->priv->cursor == NULL) { + bvw->priv->cursor = gdk_cursor_new (GDK_HAND2); + } + } else { + if (bvw->priv->cursor != NULL) { + gdk_cursor_unref (bvw->priv->cursor); + bvw->priv->cursor = NULL; + } + } + gdk_window_set_cursor (bvw->priv->video_window, + bvw->priv->cursor); + break; + case XINE_EVENT_UI_PLAYBACK_FINISHED: + if (bvw->priv->got_redirect != FALSE) + break; + + data = g_new0 (signal_data, 1); + data->signal = EOS_ASYNC; + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + break; + case XINE_EVENT_UI_CHANNELS_CHANGED: + data = g_new0 (signal_data, 1); + data->signal = CHANNELS_CHANGE_ASYNC; + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + break; + case XINE_EVENT_UI_SET_TITLE: + ui_data = event->data; + + data = g_new0 (signal_data, 1); + data->signal = TITLE_CHANGE_ASYNC; + data->msg = g_strdup (ui_data->str); + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + break; + case XINE_EVENT_PROGRESS: + prg = event->data; + + data = g_new0 (signal_data, 1); + data->signal = BUFFERING_ASYNC; + data->num = prg->percent; + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + break; + case XINE_EVENT_MRL_REFERENCE: + ref = event->data; + data = g_new0 (signal_data, 1); + data->signal = REDIRECT_ASYNC; + data->msg = g_strdup (ref->mrl); + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + bvw->priv->got_redirect = TRUE; + break; + case XINE_EVENT_UI_MESSAGE: + xine_event_message (bvw, (xine_ui_message_data_t *)event->data); + break; + case XINE_EVENT_DROPPED_FRAMES: + break; + case XINE_EVENT_AUDIO_LEVEL: + /* Unhandled, we use the software mixer, not the hardware one */ + break; + } +} + +static int +bacon_video_widget_sort_queue (gconstpointer a, gconstpointer b, gpointer data) +{ + signal_data *one, *two; + + one = (signal_data *) a; + two = (signal_data *) b; + + if (one->signal == two->signal) + return 0; + if (one->signal == ERROR_ASYNC || one->signal == MESSAGE_ASYNC) + return -1; + return 1; +} + +static void +xine_try_error (BaconVideoWidget *bvw, gboolean probe_error, GError **error) +{ + signal_data *data, *save_data; + int err; + + save_data = NULL; + + sched_yield (); + + /* Sort the queue with the errors first */ + g_async_queue_sort (bvw->priv->queue, bacon_video_widget_sort_queue, NULL); + + /* Steal messages from the async queue, if there's an error, + * to use as the error message rather than the crappy errors from + * xine_open() */ + while ((data = g_async_queue_try_pop (bvw->priv->queue)) != NULL) + { + if (data->signal == ERROR_ASYNC || data->signal == MESSAGE_ASYNC) + { + if (save_data != NULL) + { + g_free (save_data->msg); + g_free (save_data); + } + save_data = data; + } else { + g_async_queue_push (bvw->priv->queue, data); + break; + } + } + + if (save_data != NULL) + { + g_set_error (error, BVW_ERROR, save_data->num, + "%s", save_data->msg); + g_free (save_data->msg); + g_free (save_data); + + return; + } + + if (probe_error != FALSE) + return; + + err = xine_get_error (bvw->priv->stream); + if (err == XINE_ERROR_NONE) + return; + + switch (err) + { + case XINE_ERROR_NO_INPUT_PLUGIN: + g_set_error (error, BVW_ERROR, BVW_ERROR_NO_PLUGIN_FOR_FILE, + _("There is no input plugin to handle the location of this movie")); + break; + case XINE_ERROR_NO_DEMUX_PLUGIN: + g_set_error (error, BVW_ERROR, BVW_ERROR_NO_PLUGIN_FOR_FILE, + _("There is no plugin to handle this movie.")); + break; + case XINE_ERROR_DEMUX_FAILED: + g_set_error (error, BVW_ERROR, BVW_ERROR_BROKEN_FILE, + _("This movie is broken and can not be played further.")); + break; + case XINE_ERROR_MALFORMED_MRL: + g_set_error (error, BVW_ERROR, BVW_ERROR_UNVALID_LOCATION, + _("This location is not a valid one.")); + break; + case XINE_ERROR_INPUT_FAILED: + g_set_error (error, BVW_ERROR, BVW_ERROR_FILE_GENERIC, + _("This movie could not be opened.")); + break; + default: + g_set_error (error, BVW_ERROR, BVW_ERROR_GENERIC, + _("Generic Error.")); + break; + } +} + +static void +xine_error (BaconVideoWidget *bvw, GError **error) +{ + xine_try_error (bvw, FALSE, error); +} + +static void +bacon_video_widget_unrealize (GtkWidget *widget) +{ + BaconVideoWidget *bvw; + char *configfile; + int speed; + + g_return_if_fail (widget != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (widget)); + + bvw = BACON_VIDEO_WIDGET (widget); + + g_source_remove (bvw->priv->tick_id); + + speed = xine_get_param (bvw->priv->stream, XINE_PARAM_SPEED); + if (speed != XINE_SPEED_PAUSE) + show_vfx_update (bvw, FALSE); + + /* stop the playback */ + xine_stop (bvw->priv->stream); + xine_close (bvw->priv->stream); + + /* Save the current volume */ + if (bacon_video_widget_can_set_volume (bvw) != FALSE) + { + gconf_client_set_int (bvw->priv->gc, GCONF_PREFIX"/volume", + bvw->priv->volume, NULL); + } + + /* Kill the TV out */ +#ifdef HAVE_NVTV + nvtv_simple_exit(); +#endif + + xine_port_send_gui_data (bvw->priv->vo_driver, + XINE_GUI_SEND_WILL_DESTROY_DRAWABLE, + (void*)bvw->priv->video_window); + + /* Hide all windows */ + if (GTK_WIDGET_MAPPED (widget)) + gtk_widget_unmap (widget); + GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED); + + /* Get rid of the rest of the stream */ + xine_dispose (bvw->priv->stream); + xine_event_dispose_queue (bvw->priv->ev_queue); + bvw->priv->stream = NULL; + + /* save config */ + configfile = g_build_path (G_DIR_SEPARATOR_S, + g_get_home_dir (), CONFIG_FILE, NULL); + xine_config_save (bvw->priv->xine, configfile); + g_free (configfile); + + if (bvw->priv->vis != NULL) + xine_post_dispose (bvw->priv->xine, bvw->priv->vis); + + /* Kill the drivers */ + if (bvw->priv->vo_driver != NULL) + xine_close_video_driver (bvw->priv->xine, + bvw->priv->vo_driver); + if (bvw->priv->ao_driver != NULL) + xine_close_audio_driver (bvw->priv->xine, + bvw->priv->ao_driver); + + /* stop event thread */ + xine_plugins_garbage_collector (bvw->priv->xine); + xine_exit (bvw->priv->xine); + bvw->priv->xine = NULL; + + /* This destroys widget->window and unsets the realized flag */ + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (*GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +GOptionGroup * +bacon_video_widget_get_option_group (void) +{ + return g_option_group_new ("xine", "Show xine-lib Options", + "xine-lib Options", NULL, NULL); +} + +void +bacon_video_widget_init_backend (int *argc, char ***argv) +{ + /* no-op */ +} + +GQuark +bacon_video_widget_error_quark (void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string ("bvw-error-quark"); + return q; +} + +GtkWidget * +bacon_video_widget_new (int width, int height, + BvwUseType type, GError **error) +{ + BaconVideoWidget *bvw; + xine_cfg_entry_t entry; + + bvw = BACON_VIDEO_WIDGET (g_object_new + (bacon_video_widget_get_type (), NULL)); + + bvw->priv->init_width = width; + bvw->priv->init_height = height; + bvw->priv->type = type; + bvw->priv->audio_out_type = -1; + + /* Don't load anything yet if we're looking for proper video + * output */ + if (type == BVW_USE_TYPE_VIDEO) + { + bvw_config_helper_num (bvw->priv->xine, "engine.buffers.video_num_buffers", + 500, &entry); + entry.num_value = 500; + xine_config_update_entry (bvw->priv->xine, &entry); + return GTK_WIDGET (bvw); + } + + /* load the output drivers */ + if (type == BVW_USE_TYPE_AUDIO) + { + BaconVideoWidgetAudioOutType type; + + bvw->priv->ao_driver = load_audio_out_driver (bvw, + FALSE, error); + if (error != NULL && *error != NULL) + return NULL; + type = bacon_video_widget_get_audio_out_type (bvw); + bacon_video_widget_set_audio_out_type (bvw, type); + } else if (type == BVW_USE_TYPE_METADATA) { + bvw->priv->ao_driver = load_audio_out_driver (bvw, + TRUE, error); + } + + /* We need to wait for the widget to realise if we want to + * load a video output with screen output, and capture is the + * only one actually needing a video output */ + if (type == BVW_USE_TYPE_CAPTURE || type == BVW_USE_TYPE_METADATA) { + bvw->priv->vo_driver = load_video_out_driver (bvw, type); + } + + /* Be extra careful about exiting out nicely when a video output + * isn't available */ + if (type == BVW_USE_TYPE_CAPTURE && bvw->priv->vo_driver == NULL) + { + /* Close the xine stuff */ + if (bvw->priv->ao_driver != NULL) { + xine_close_audio_driver (bvw->priv->xine, + bvw->priv->ao_driver); + } + xine_exit (bvw->priv->xine); + bvw->priv->xine = NULL; + + /* get rid of all our crappety crap */ + g_source_remove (bvw->priv->tick_id); + g_idle_remove_by_data (bvw); + g_async_queue_unref (bvw->priv->queue); + g_free (bvw->priv->vis_name); + g_object_unref (G_OBJECT (bvw->priv->gc)); + g_free (bvw->priv); + g_free (bvw); + + g_set_error (error, BVW_ERROR, BVW_ERROR_VIDEO_PLUGIN, + _("No video output is available. Make sure that the program is correctly installed.")); + return NULL; + } + + bvw_config_helper_num (bvw->priv->xine, "engine.buffers.video_num_buffers", + 5, &entry); + entry.num_value = 5; + xine_config_update_entry (bvw->priv->xine, &entry); + + bvw->priv->stream = xine_stream_new (bvw->priv->xine, + bvw->priv->ao_driver, bvw->priv->vo_driver); + setup_config_stream (bvw); + bvw->priv->ev_queue = xine_event_new_queue (bvw->priv->stream); + xine_event_create_listener_thread (bvw->priv->ev_queue, + xine_event, (void *) bvw); + + return GTK_WIDGET (bvw); +} + +static gboolean +bacon_video_widget_expose (GtkWidget *widget, GdkEventExpose *event) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) widget; + gboolean draw_logo, has_video; + + /* if there's only audio and no visualisation, draw the logo as well */ + has_video = xine_get_stream_info(bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO); + draw_logo = !has_video && !bvw->priv->using_vfx; + + if (bvw->priv->logo_mode == FALSE && draw_logo == FALSE) { + XExposeEvent *expose; + + if (event->count != 0) + return TRUE; + + expose = g_new0 (XExposeEvent, 1); + expose->count = event->count; + + xine_port_send_gui_data (bvw->priv->vo_driver, + XINE_GUI_SEND_EXPOSE_EVENT, expose); + + g_free (expose); + } else { + int s_width, s_height, w_width, w_height; + GdkPixbuf *logo = NULL; + gfloat ratio; + + /* Start with a nice black canvas */ + gdk_draw_rectangle (widget->window, widget->style->black_gc, + TRUE, 0, 0, + widget->allocation.width, + widget->allocation.height); + + if (bvw->priv->logo_pixbuf == NULL) + return FALSE; + + s_width = gdk_pixbuf_get_width (bvw->priv->logo_pixbuf); + s_height = gdk_pixbuf_get_height (bvw->priv->logo_pixbuf); + w_width = widget->allocation.width; + w_height = widget->allocation.height; + + if ((gfloat) w_width / s_width > (gfloat) w_height / s_height) { + ratio = (gfloat) w_height / s_height; + } else { + ratio = (gfloat) w_width / s_width; + } + + s_width *= ratio; + s_height *= ratio; + + if (s_width <= 1 || s_height <= 1) + return FALSE; + + logo = gdk_pixbuf_scale_simple (bvw->priv->logo_pixbuf, + s_width, s_height, GDK_INTERP_BILINEAR); + + gdk_draw_pixbuf (widget->window, widget->style->fg_gc[0], logo, + 0, 0, + (w_width - s_width) / 2, + (w_height - s_height) / 2, + s_width, s_height, GDK_RGB_DITHER_NONE, 0, 0); + + gdk_pixbuf_unref (logo); + } + + return TRUE; +} + +static gboolean +bacon_video_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) widget; + + generate_mouse_event (bvw, (GdkEvent *)event, TRUE); + + if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event != NULL) + (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event); + + return FALSE; +} + +static gboolean +bacon_video_widget_button_press (GtkWidget *widget, GdkEventButton *event) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) widget; + + /* Don't propagate double-click events if we just had a button + * event consumed internally */ + if (event->type == GDK_2BUTTON_PRESS && bvw->priv->bevent_consumed != FALSE) { + bvw->priv->bevent_consumed = FALSE; + return TRUE; + } + + /* If the event was consumed, mark it as such */ + if (generate_mouse_event (bvw, (GdkEvent *)event, FALSE) != FALSE && bvw->priv->cursor != NULL) { + bvw->priv->bevent_consumed = TRUE; + return FALSE; + } + + if (GTK_WIDGET_CLASS (parent_class)->button_press_event != NULL) + (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event); + + return FALSE; +} + +static void +bacon_video_widget_show (GtkWidget *widget) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) widget; + + gdk_window_show (bvw->priv->video_window); + + if (GTK_WIDGET_CLASS (parent_class)->show != NULL) + (* GTK_WIDGET_CLASS (parent_class)->show) (widget); +} + +static void +bacon_video_widget_hide (GtkWidget *widget) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) widget; + + gdk_window_hide (bvw->priv->video_window); + + if (GTK_WIDGET_CLASS (parent_class)->hide != NULL) + (* GTK_WIDGET_CLASS (parent_class)->hide) (widget); +} + +static void +bacon_video_widget_size_request (GtkWidget *widget, GtkRequisition *requisition) +{ + g_return_if_fail(widget != NULL); + g_return_if_fail(BACON_IS_VIDEO_WIDGET(widget)); + + requisition->width = 240; + requisition->height = 180; +} + +static void +bacon_video_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + BaconVideoWidget *bvw; + + g_return_if_fail (widget != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (widget)); + + bvw = BACON_VIDEO_WIDGET (widget); + + widget->allocation = *allocation; + bvw->priv->xpos = allocation->x; + bvw->priv->ypos = allocation->y; + + if ( (bvw->priv->init_width == 0) && (bvw->priv->init_height == 0) ) { + /* First allocation, saving values */ + bvw->priv->init_width = allocation->width; + bvw->priv->init_height = allocation->height; + } + + if (GTK_WIDGET_REALIZED (widget)) + { + gdk_window_move_resize (widget->window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + } +} + +static gboolean +bacon_video_widget_tick_send (BaconVideoWidget *bvw) +{ + int current_time, stream_length, current_position; + float current_position_f; + gboolean ret = TRUE, seekable; + + if (bvw->priv->stream == NULL || bvw->priv->logo_mode != FALSE) + return TRUE; + + if (bvw->com->mrl == NULL) + { + current_time = 0; + stream_length = 0; + current_position = 0; + } else { + ret = xine_get_pos_length (bvw->priv->stream, + ¤t_position, + ¤t_time, + &stream_length); + } + + if (ret == FALSE) + return TRUE; + + if (bvw->priv->seeking == 1) + { + current_position_f = bvw->priv->seek_dest; + current_time = bvw->priv->seek_dest * stream_length; + } else if (bvw->priv->seeking == 2) { + current_time = bvw->priv->seek_dest_time; + current_position_f = (float) current_time / stream_length; + } else { + current_position_f = (float) current_position / 65535; + } + + if (stream_length > 0) + bvw->priv->is_live = FALSE; + else + bvw->priv->is_live = TRUE; + + if (stream_length != 0 && bvw->com->mrl != NULL) { + seekable = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_SEEKABLE); + } else { + seekable = FALSE; + } + + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[TICK], 0, + (gint64) (current_time), + (gint64) (stream_length), + current_position_f, + seekable); + + return TRUE; +} + +static void +show_vfx_update (BaconVideoWidget *bvw, gboolean show_visuals) +{ + xine_post_out_t *audio_source; + gboolean has_video, enable; + + if (bvw->priv->vis_name == NULL) + return; + + has_video = xine_get_stream_info(bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO); + enable = FALSE; + + /* Already has video, and we were showing visual effects */ + if (has_video != FALSE && show_visuals != FALSE + && bvw->priv->using_vfx != FALSE) + { + enable = FALSE; + /* Doesn't have video, should show visual effects, and wasn't doing + * so before */ + } else if (has_video == FALSE && show_visuals != FALSE + && bvw->priv->using_vfx == FALSE) + { + if (bvw->priv->vis == NULL) { + bvw->priv->vis = xine_post_init (bvw->priv->xine, + bvw->priv->vis_name, 0, + &bvw->priv->ao_driver, + &bvw->priv->vo_driver); + } + if (bvw->priv->vis == NULL && strcmp (bvw->priv->vis_name, "goom") != 0) { + bvw->priv->vis = xine_post_init (bvw->priv->xine, + "goom", 0, + &bvw->priv->ao_driver, + &bvw->priv->vo_driver); + } + if (bvw->priv->vis != NULL) { + enable = TRUE; + } + /* Doesn't have video, but visual effects are disabled */ + } else if (has_video == FALSE && show_visuals == FALSE) { + enable = FALSE; + /* No changes */ + } else { + return; + } + + if (enable == FALSE) { + audio_source = xine_get_audio_source (bvw->priv->stream); + if (xine_post_wire_audio_port (audio_source, + bvw->priv->ao_driver)) { + bvw->priv->using_vfx = FALSE; + + /* Queue a redraw of the widget */ + gtk_widget_queue_draw (GTK_WIDGET (bvw)); + } + if (bvw->priv->vis != NULL) { + xine_post_dispose (bvw->priv->xine, + bvw->priv->vis); + bvw->priv->vis = NULL; + } + } else { + audio_source = xine_get_audio_source (bvw->priv->stream); + if (xine_post_wire_audio_port (audio_source, + bvw->priv->vis->audio_input[0])) { + bvw->priv->using_vfx = TRUE; + + /* Queue a redraw of the widget */ + gtk_widget_queue_draw (GTK_WIDGET (bvw)); + } + } +} + +static char * +get_fourcc_string (uint32_t f) +{ + char fcc[5]; + + memset(&fcc, 0, sizeof(fcc)); + + /* Should we take care about endianess ? */ + fcc[0] = f | 0xFFFFFF00; + fcc[1] = f>>8 | 0xFFFFFF00; + fcc[2] = f>>16 | 0xFFFFFF00; + fcc[3] = f>>24 | 0xFFFFFF00; + fcc[4] = 0; + + if(f <= 0xFFFF) + sprintf(fcc, "0x%x", f); + + if((fcc[0] == 'm') && (fcc[1] == 's')) + { + if((fcc[2] = 0x0) && (fcc[3] == 0x55)) + *(uint32_t *) fcc = 0x33706d2e; /* Force to '.mp3' */ + } + + return g_strdup (fcc); +} + +static char * +bacon_video_widget_get_nice_codec_name (BaconVideoWidget *bvw, + gboolean is_audio) +{ + char *name; + int codec, fcc; + + if (is_audio == FALSE) { + codec = XINE_META_INFO_VIDEOCODEC; + fcc = XINE_STREAM_INFO_VIDEO_FOURCC; + } else { + codec = XINE_META_INFO_AUDIOCODEC; + fcc = XINE_STREAM_INFO_AUDIO_FOURCC; + } + + name = g_strdup (xine_get_meta_info (bvw->priv->stream, codec)); + + if (name == NULL || name[0] == '\0') + { + guint32 fourcc; + + g_free (name); + fourcc = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_VIDEO_FOURCC); + name = get_fourcc_string (fourcc); + } + + return name; +} + +char * +bacon_video_widget_get_backend_name (BaconVideoWidget *bvw) +{ + return g_strdup_printf ("xine-lib version %s", + xine_get_version_string ()); +} + +static char * +bacon_video_widget_get_subtitled (const char *mrl, const char *subtitle_uri) +{ + g_return_val_if_fail (g_str_has_prefix (subtitle_uri, "file://"), NULL); + return g_strdup_printf ("%s#subtitle:%s", mrl, subtitle_uri + strlen ("file://")); +} + +static void +bacon_video_widget_open_async_error (BaconVideoWidget *bvw, GError *error) +{ + signal_data *sigdata; + + sigdata = g_new0 (signal_data, 1); + sigdata->signal = ERROR_ASYNC; + sigdata->msg = g_strdup (error->message); + sigdata->fatal = FALSE; + g_async_queue_push (bvw->priv->queue, sigdata); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); +} + +static gpointer +bacon_video_widget_open_thread (gpointer data) +{ + BaconVideoWidget *bvw = (BaconVideoWidget *) data; + GError *error = NULL; + int err; + + err = xine_open (bvw->priv->stream, bvw->com->mrl); + if (err == 0) { + xine_error (bvw, &error); + bacon_video_widget_close (bvw); + bacon_video_widget_open_async_error (bvw, error); + g_error_free (error); + } else { + xine_try_error (bvw, TRUE, &error); + if (error != NULL) { + bacon_video_widget_close (bvw); + bacon_video_widget_open_async_error (bvw, error); + g_error_free (error); + } + } + + bvw->priv->open_thread = NULL; + return NULL; +} + +static gboolean +bacon_video_widget_open_async (BaconVideoWidget *bvw, const char *mrl, + GError **error) +{ + bvw->priv->open_thread = g_thread_create (bacon_video_widget_open_thread, bvw, TRUE, error); + if (bvw->priv->open_thread == NULL) + return FALSE; + + return TRUE; +} + +gboolean +bacon_video_widget_open_with_subtitle (BaconVideoWidget *bvw, const char *mrl, + const char *subtitle_uri, GError **error) +{ + int err; + + g_return_val_if_fail (mrl != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + g_return_val_if_fail (bvw->com->mrl == NULL, FALSE); + + bvw->priv->got_redirect = FALSE; + + /* Hack to get VCD playback from .cue files */ + if (g_str_has_prefix (mrl, "vcd:/") != FALSE + && g_str_has_suffix (mrl, ".cue") != FALSE) { + bvw->com->mrl = g_strdup_printf ("%s@", mrl); + } else if (g_str_has_prefix (mrl, "icy:") != FALSE) { + /* Handle "icy://" URLs from QuickTime */ + bvw->com->mrl = g_strdup_printf ("http:%s", mrl + 4); + } else { + bvw->com->mrl = g_strdup (mrl); + } + + if (g_str_has_prefix (mrl, "fd://") != FALSE) { + if (subtitle_uri != NULL) + g_warning ("%s passed along with a subtitle URI", mrl); + return bacon_video_widget_open_async (bvw, mrl, error); + } + + if (subtitle_uri != NULL) { + char *subtitled; + subtitled = bacon_video_widget_get_subtitled (mrl, subtitle_uri); + if (subtitled != NULL) { + err = xine_open (bvw->priv->stream, subtitled); + bvw->priv->has_subtitle = TRUE; + g_free (subtitled); + } else { + err = xine_open (bvw->priv->stream, bvw->com->mrl); + } + } else { + err = xine_open (bvw->priv->stream, bvw->com->mrl); + } + + xine_plugins_garbage_collector (bvw->priv->xine); + + if (err == 0) { + bacon_video_widget_close (bvw); + xine_error (bvw, error); + return FALSE; + } else { + xine_try_error (bvw, TRUE, error); + if (error != NULL && *error != NULL) { + bacon_video_widget_close (bvw); + return FALSE; + } + } + + if (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_VIDEO_HANDLED) == FALSE + || (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO) == FALSE + && xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_AUDIO_HANDLED) == FALSE)) + { + char *name; + gboolean is_audio; + + is_audio = (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO) == FALSE); + + name = bacon_video_widget_get_nice_codec_name (bvw, is_audio); + + bacon_video_widget_close (bvw); + + if (is_audio == FALSE) { + g_set_error (error, BVW_ERROR, + BVW_ERROR_CODEC_NOT_HANDLED, + _("Video codec '%s' is not handled. You might need to install additional plugins to be able to play some types of movies"), name); + } else { + g_set_error (error, BVW_ERROR, + BVW_ERROR_CODEC_NOT_HANDLED, + _("Audio codec '%s' is not handled. You might need to install additional plugins to be able to play some types of movies"), name); + } + + g_free (name); + + return FALSE; + } + + if (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO) == FALSE + && bvw->priv->type != BVW_USE_TYPE_METADATA + && bvw->priv->ao_driver == NULL) + { + bacon_video_widget_close (bvw); + + g_set_error (error, BVW_ERROR, BVW_ERROR_AUDIO_ONLY, + _("This is an audio-only file, and there is no audio output available.")); + + return FALSE; + } + + show_vfx_update (bvw, bvw->priv->show_vfx); + + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[GOT_METADATA], 0, NULL); + + return TRUE; +} + +gboolean +bacon_video_widget_play (BaconVideoWidget *bvw, GError **gerror) +{ + int error; + + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + g_return_val_if_fail (bvw->priv->xine != NULL, -1); + + error = 1; + + if (bvw->priv->seeking == 1) + { + error = xine_play (bvw->priv->stream, + bvw->priv->seek_dest * 65535, 0); + bvw->priv->seeking = 0; + } else if (bvw->priv->seeking == 2) { + error = xine_play (bvw->priv->stream, 0, + bvw->priv->seek_dest_time); + bvw->priv->seeking = 0; + } else { + int speed, status; + + speed = xine_get_param (bvw->priv->stream, XINE_PARAM_SPEED); + status = xine_get_status (bvw->priv->stream); + if (speed != XINE_SPEED_NORMAL && status == XINE_STATUS_PLAY) + { + xine_set_param (bvw->priv->stream, + XINE_PARAM_SPEED, XINE_SPEED_NORMAL); + } else { + error = xine_play (bvw->priv->stream, 0, 0); + } + + bvw->priv->seeking = 0; + } + + if (error == 0) + { + xine_error (bvw, gerror); + return FALSE; + } + + if (bvw->priv->queued_vis != NULL) + { + bacon_video_widget_set_visuals (bvw, bvw->priv->queued_vis); + g_free (bvw->priv->queued_vis); + bvw->priv->queued_vis = NULL; + } + + /* Workaround for xine-lib: don't try to use a + * non-existent audio channel */ + { + int cur, num; + + cur = xine_get_param(bvw->priv->stream, + XINE_PARAM_AUDIO_CHANNEL_LOGICAL); + num = xine_get_stream_info(bvw->priv->stream, + XINE_STREAM_INFO_AUDIO_CHANNELS); + if (cur > num) + xine_set_param(bvw->priv->stream, + XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1); + } + + return TRUE; +} + +gboolean +bacon_video_widget_can_direct_seek (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + + return bacon_video_widget_common_can_direct_seek (bvw->com); +} + +gboolean bacon_video_widget_seek (BaconVideoWidget *bvw, float position, + GError **gerror) +{ + int error, speed; + + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + g_return_val_if_fail (bvw->priv->xine != NULL, -1); + + speed = xine_get_param (bvw->priv->stream, XINE_PARAM_SPEED); + if (speed == XINE_SPEED_PAUSE) + { + bvw->priv->seeking = 1; + bvw->priv->seek_dest = position; + return TRUE; + } + + error = xine_play (bvw->priv->stream, position * 65535, 0); + + if (error == 0) + { + xine_error (bvw, gerror); + return FALSE; + } + + return TRUE; +} + +gboolean bacon_video_widget_seek_time (BaconVideoWidget *bvw, gint64 time, + GError **gerror) +{ + int error, speed, status; + gint64 length; + + g_return_val_if_fail (bvw != NULL, -1); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + g_return_val_if_fail (bvw->priv->xine != NULL, -1); + + length = bacon_video_widget_get_stream_length (bvw); + + speed = xine_get_param (bvw->priv->stream, XINE_PARAM_SPEED); + status = xine_get_status (bvw->priv->stream); + if (speed == XINE_SPEED_PAUSE || status == XINE_STATUS_STOP) + { + bvw->priv->seeking = 2; + bvw->priv->seek_dest_time = CLAMP (time, 0, length); + return TRUE; + } + + if (time > length && g_str_has_prefix (bvw->com->mrl, "dvd:") == FALSE && g_str_has_prefix (bvw->com->mrl, "vcd:") == FALSE) { + signal_data *data; + + data = g_new0 (signal_data, 1); + data->signal = EOS_ASYNC; + g_async_queue_push (bvw->priv->queue, data); + g_idle_add ((GSourceFunc) bacon_video_widget_idle_signal, bvw); + return TRUE; + } + + error = xine_play (bvw->priv->stream, 0, CLAMP (time, 0, length)); + + if (error == 0) + { + xine_error (bvw, gerror); + return FALSE; + } + + return TRUE; +} + +void +bacon_video_widget_stop (BaconVideoWidget *bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + xine_stop (bvw->priv->stream); +} + +void +bacon_video_widget_close (BaconVideoWidget *bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + if (bvw->priv->open_thread != NULL + && g_thread_self () != bvw->priv->open_thread) { + /* Nicely wait for the timeout */ + g_thread_join (bvw->priv->open_thread); + bvw->priv->open_thread = NULL; + } + + xine_stop (bvw->priv->stream); + xine_close (bvw->priv->stream); + bvw->priv->has_subtitle = FALSE; + g_free (bvw->com->mrl); + bvw->com->mrl = NULL; + + if (bvw->priv->logo_mode == FALSE) + g_signal_emit (G_OBJECT (bvw), + bvw_table_signals[CHANNELS_CHANGE], 0, NULL); +} + +/* Properties */ +static void +bacon_video_widget_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + BaconVideoWidget *bvw; + + bvw = BACON_VIDEO_WIDGET (object); + + switch (property_id) + { + case PROP_LOGO_MODE: + bacon_video_widget_set_logo_mode (bvw, + g_value_get_boolean (value)); + break; + case PROP_SHOWCURSOR: + bacon_video_widget_set_show_cursor (bvw, + g_value_get_boolean (value)); + break; + case PROP_MEDIADEV: + bacon_video_widget_set_media_device (bvw, + g_value_get_string (value)); + break; + case PROP_SHOW_VISUALS: + bacon_video_widget_set_show_visuals (bvw, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +bacon_video_widget_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + BaconVideoWidget *bvw; + + bvw = BACON_VIDEO_WIDGET (object); + + switch (property_id) + { + case PROP_LOGO_MODE: + g_value_set_boolean (value, + bacon_video_widget_get_logo_mode (bvw)); + break; + case PROP_POSITION: + g_value_set_int64 (value, bacon_video_widget_get_position (bvw)); + break; + case PROP_STREAM_LENGTH: + g_value_set_int64 (value, + bacon_video_widget_get_stream_length (bvw)); + break; + case PROP_PLAYING: + g_value_set_boolean (value, + bacon_video_widget_is_playing (bvw)); + break; + case PROP_SEEKABLE: + g_value_set_boolean (value, + bacon_video_widget_is_seekable (bvw)); + break; + case PROP_SHOWCURSOR: + g_value_set_boolean (value, + bacon_video_widget_get_show_cursor (bvw)); + break; + case PROP_MEDIADEV: + g_value_set_string (value, bvw->priv->mediadev); + break; + case PROP_VOLUME: + g_value_set_int (value, bvw->priv->volume); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +void +bacon_video_widget_set_logo_mode (BaconVideoWidget *bvw, gboolean logo_mode) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + if (logo_mode != bvw->priv->logo_mode) { + bvw->priv->logo_mode = logo_mode; + + /* Queue a redraw of the widget */ + gtk_widget_queue_draw (GTK_WIDGET (bvw)); + + /* And set a decent size for the video output */ + if (logo_mode != FALSE && bvw->priv->logo_pixbuf != NULL) { + bvw->priv->video_width = gdk_pixbuf_get_width (bvw->priv->logo_pixbuf); + bvw->priv->video_height = gdk_pixbuf_get_height (bvw->priv->logo_pixbuf); + } else if (logo_mode != FALSE) { + bvw->priv->video_width = DEFAULT_WIDTH; + bvw->priv->video_height = DEFAULT_HEIGHT; + } + } + g_object_notify (G_OBJECT (bvw), "logo_mode"); +} + +void +bacon_video_widget_set_logo (BaconVideoWidget *bvw, char *filename) +{ + GError *err = NULL; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET(bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (filename != NULL); + + if (bvw->priv->logo_pixbuf != NULL) + g_object_unref (bvw->priv->logo_pixbuf); + + bvw->priv->logo_pixbuf = gdk_pixbuf_new_from_file (filename, &err); + if (err) { + g_warning ("Couldn't open logo image: %s", + err->message ? err->message : "No reason"); + g_error_free (err); + } +} + +void +bacon_video_widget_set_logo_pixbuf (BaconVideoWidget *bvw, GdkPixbuf *logo) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET(bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (logo != NULL); + + if (bvw->priv->logo_pixbuf != NULL) + g_object_unref (bvw->priv->logo_pixbuf); + + g_object_ref (logo); + bvw->priv->logo_pixbuf = logo; +} + +gboolean +bacon_video_widget_get_logo_mode (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + return bvw->priv->logo_mode; +} + +void +bacon_video_widget_pause (BaconVideoWidget *bvw) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + xine_set_param (bvw->priv->stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE); + + if (bvw->priv->is_live != FALSE) + xine_stop (bvw->priv->stream); + + /* Close the audio device when on pause */ + xine_set_param (bvw->priv->stream, + XINE_PARAM_AUDIO_CLOSE_DEVICE, 1); +} + +float +bacon_video_widget_get_position (BaconVideoWidget *bvw) +{ + int pos_stream = 0, i = 0; + int pos_time, length_time; + gboolean ret; + + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (bvw->com->mrl == NULL) + return 0; + + if (bacon_video_widget_is_playing (bvw) == FALSE) + return 0; + + ret = xine_get_pos_length (bvw->priv->stream, &pos_stream, + &pos_time, &length_time); + + while (ret == FALSE && i < 10) + { + usleep (100000); + ret = xine_get_pos_length (bvw->priv->stream, &pos_stream, + &pos_time, &length_time); + i++; + } + + if (bvw->priv->seeking == 1) + { + return bvw->priv->seek_dest * length_time; + } else if (bvw->priv->seeking == 2) { + return bvw->priv->seek_dest_time; + } + + if (ret == FALSE) + return -1; + + return pos_stream / 65535; +} + +gboolean +bacon_video_widget_can_set_volume (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (bvw->priv->ao_driver == NULL || bvw->priv->ao_driver_none != FALSE) + return FALSE; + + if (bvw->priv->audio_out_type == BVW_AUDIO_SOUND_AC3PASSTHRU) + return FALSE; + + if (xine_get_param (bvw->priv->stream, + XINE_PARAM_AUDIO_CHANNEL_LOGICAL) == -2) + return FALSE; + + return TRUE; +} + +void +bacon_video_widget_set_volume (BaconVideoWidget *bvw, int volume) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + if (bacon_video_widget_can_set_volume (bvw) != FALSE) + { + volume = CLAMP (volume, 0, 100); + xine_set_param (bvw->priv->stream, + XINE_PARAM_AUDIO_AMP_LEVEL, volume); + bvw->priv->volume = volume; + g_object_notify (G_OBJECT (bvw), "volume"); + } +} + +int +bacon_video_widget_get_volume (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (bacon_video_widget_can_set_volume (bvw) == FALSE) + return 0; + + return bvw->priv->volume; +} + +gboolean +bacon_video_widget_fullscreen_mode_available (BaconVideoWidget *bvw, + TvOutType tvout) +{ + switch(tvout) { + case TV_OUT_NONE: + /* Assume that ordinary fullscreen always works */ + return TRUE; + case TV_OUT_NVTV_NTSC: + case TV_OUT_NVTV_PAL: +#ifdef HAVE_NVTV + /* Make sure nvtv is initialized, it will not do any harm + * if it is done twice any way */ + if (!(nvtv_simple_init() && nvtv_enable_autoresize(TRUE))) { + nvtv_simple_enable(FALSE); + } + return (nvtv_simple_is_available()); +#else + return FALSE; +#endif + default: + g_assert_not_reached (); + } + + return FALSE; +} + +void +bacon_video_widget_set_fullscreen (BaconVideoWidget *bvw, gboolean fullscreen) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + if (bvw->priv->have_xvidmode == FALSE && + bvw->priv->tvout != TV_OUT_NVTV_NTSC && + bvw->priv->tvout != TV_OUT_NVTV_PAL) + return; + + bvw->priv->fullscreen_mode = fullscreen; + + if (fullscreen == FALSE) + { +#ifdef HAVE_NVTV + /* If NVTV is used */ + if (nvtv_simple_get_state() == NVTV_SIMPLE_TV_ON) { + nvtv_simple_switch(NVTV_SIMPLE_TV_OFF,0,0); + + /* Else if just auto resize is used */ + } else if (bvw->priv->auto_resize != FALSE) { +#endif + bacon_restore (); +#ifdef HAVE_NVTV + } + /* Turn fullscreen on with NVTV if that option is on */ + } else if ((bvw->priv->tvout == TV_OUT_NVTV_NTSC) || + (bvw->priv->tvout == TV_OUT_NVTV_PAL)) { + nvtv_simple_switch(NVTV_SIMPLE_TV_ON, + bvw->priv->video_width, + bvw->priv->video_height); +#endif + /* Turn fullscreen on when we have xvidmode */ + } else if (bvw->priv->have_xvidmode != FALSE) { + bacon_resize (); + } +} + +void +bacon_video_widget_set_show_cursor (BaconVideoWidget *bvw, + gboolean show_cursor) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + + if (show_cursor == FALSE) + { + totem_gdk_window_set_invisible_cursor (bvw->priv->video_window); + } else { + gdk_window_set_cursor (bvw->priv->video_window, + bvw->priv->cursor); + } + + bvw->priv->cursor_shown = show_cursor; +} + +gboolean +bacon_video_widget_get_show_cursor (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + return bvw->priv->cursor_shown; +} + +void +bacon_video_widget_set_media_device (BaconVideoWidget *bvw, const char *path) +{ + xine_cfg_entry_t entry; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (path != NULL); + + g_free (bvw->priv->mediadev); + + /* DVD device */ + bvw_config_helper_string (bvw->priv->xine, "media.dvd.device", + path, &entry); + entry.str_value = (char *) path; + xine_config_update_entry (bvw->priv->xine, &entry); + + /* VCD device */ + bvw_config_helper_string (bvw->priv->xine, "media.vcd.device", + path, &entry); + entry.str_value = (char *) path; + xine_config_update_entry (bvw->priv->xine, &entry); + + /* VCD device for the new input plugin */ + bvw_config_helper_string (bvw->priv->xine, "media.vcd.device", + path, &entry); + entry.str_value = (char *) path; + xine_config_update_entry (bvw->priv->xine, &entry); + + /* CDDA device */ + bvw_config_helper_string (bvw->priv->xine, "media.audio_cd.device", + path, &entry); + entry.str_value = (char *) path; + xine_config_update_entry (bvw->priv->xine, &entry); + + bvw->priv->mediadev = g_strdup (path); +} + +void +bacon_video_widget_set_connection_speed (BaconVideoWidget *bvw, int speed) +{ + xine_cfg_entry_t entry; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (speed >= 0); + /* -1 for the NULL, -1 for the indexing from 0 */ + g_return_if_fail (speed <= (int) (G_N_ELEMENTS (mms_bandwidth_strs) - 2)); + + xine_config_register_enum (bvw->priv->xine, + "media.network.bandwidth", + 6, + (char **) mms_bandwidth_strs, + "Network bandwidth", + NULL, 0, NULL, NULL); + + xine_config_lookup_entry (bvw->priv->xine, + "media.network.bandwidth", &entry); + entry.num_value = speed; + xine_config_update_entry (bvw->priv->xine, &entry); +} + +int +bacon_video_widget_get_connection_speed (BaconVideoWidget *bvw) +{ + xine_cfg_entry_t entry; + + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + xine_config_register_enum (bvw->priv->xine, + "media.network.bandwidth", + 6, + (char **) mms_bandwidth_strs, + "Network bandwidth", + NULL, 0, NULL, NULL); + + xine_config_lookup_entry (bvw->priv->xine, + "media.network.bandwidth", &entry); + + return entry.num_value; +} + +void +bacon_video_widget_set_deinterlacing (BaconVideoWidget *bvw, + gboolean deinterlace) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + xine_set_param (bvw->priv->stream, XINE_PARAM_VO_DEINTERLACE, + deinterlace); +} + +gboolean +bacon_video_widget_get_deinterlacing (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + return xine_get_param (bvw->priv->stream, XINE_PARAM_VO_DEINTERLACE); +} + +void +bacon_video_widget_set_tv_out (BaconVideoWidget *bvw, TvOutType tvout) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + +#ifdef HAVE_NVTV + if (tvout == TV_OUT_NVTV_PAL) { + nvtv_simple_set_tvsystem(NVTV_SIMPLE_TVSYSTEM_PAL); + } else if (tvout == TV_OUT_NVTV_NTSC) { + nvtv_simple_set_tvsystem(NVTV_SIMPLE_TVSYSTEM_NTSC); + } +#endif + + bvw->priv->tvout = tvout; +} + +TvOutType +bacon_video_widget_get_tv_out (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + return bvw->priv->tvout; +} + +gboolean +bacon_video_widget_set_show_visuals (BaconVideoWidget *bvw, + gboolean show_visuals) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + bvw->priv->show_vfx = show_visuals; + show_vfx_update (bvw, show_visuals); + + return TRUE; +} + +GList * +bacon_video_widget_get_visuals_list (BaconVideoWidget *bvw) +{ + const char * const* plugins; + int i; + + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (bvw->priv->xine != NULL, NULL); + + if (bvw->priv->visuals != NULL) + return bvw->priv->visuals; + + plugins = xine_list_post_plugins_typed (bvw->priv->xine, + XINE_POST_TYPE_AUDIO_VISUALIZATION); + + for (i = 0; plugins[i] != NULL; i++) + { + bvw->priv->visuals = g_list_prepend + (bvw->priv->visuals, g_strdup (plugins[i])); + } + + bvw->priv->visuals = g_list_reverse (bvw->priv->visuals); + + return bvw->priv->visuals; +} + +gboolean +bacon_video_widget_set_visuals (BaconVideoWidget *bvw, const char *name) +{ + int speed; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + if (bvw->priv->type != BVW_USE_TYPE_VIDEO) + return FALSE; + + if (GTK_WIDGET_REALIZED (bvw) == FALSE) + { + g_free (bvw->priv->vis_name); + bvw->priv->vis_name = g_strdup (name); + return FALSE; + } + + speed = xine_get_param (bvw->priv->stream, XINE_PARAM_SPEED); + if (speed == XINE_SPEED_PAUSE && bvw->priv->using_vfx != FALSE) + { + g_free (bvw->priv->queued_vis); + if (strcmp (name, bvw->priv->vis_name) == 0) + { + bvw->priv->queued_vis = NULL; + } else { + bvw->priv->queued_vis = g_strdup (name); + } + return FALSE; + } + + if (bvw->priv->using_vfx != FALSE) { + show_vfx_update (bvw, FALSE); + g_free (bvw->priv->vis_name); + bvw->priv->vis_name = g_strdup (name); + show_vfx_update (bvw, TRUE); + } else { + g_free (bvw->priv->vis_name); + bvw->priv->vis_name = g_strdup (name); + show_vfx_update (bvw, FALSE); + } + + return FALSE; +} + +static void +bacon_video_widget_set_visuals_quality_size (BaconVideoWidget *bvw, + int w, int h, int fps) +{ + xine_cfg_entry_t entry; + + bvw_config_helper_num (bvw->priv->xine, "effects.goom.fps", fps, &entry); + entry.num_value = fps; + xine_config_update_entry (bvw->priv->xine, &entry); + + bvw_config_helper_num (bvw->priv->xine, "effects.goom.width", w, &entry); + entry.num_value = w; + xine_config_update_entry (bvw->priv->xine, &entry); + + bvw_config_helper_num (bvw->priv->xine, "effects.goom.height", h, &entry); + entry.num_value = h; + xine_config_update_entry (bvw->priv->xine, &entry); +} + +void +bacon_video_widget_set_visuals_quality (BaconVideoWidget *bvw, + VisualsQuality quality) +{ + GdkScreen *screen; + int fps, h, w; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + if (bacon_video_widget_common_get_vis_quality (quality, &h, &fps) == FALSE) + return; + + screen = gtk_widget_get_screen (GTK_WIDGET (bvw)); + w = h * gdk_screen_get_width (screen) / gdk_screen_get_height (screen); + bacon_video_widget_set_visuals_quality_size (bvw, w, h, fps); + + bvw->priv->quality = quality; +} + +gboolean +bacon_video_widget_get_auto_resize (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + return bvw->priv->auto_resize; +} + +void +bacon_video_widget_set_auto_resize (BaconVideoWidget *bvw, + gboolean auto_resize) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + bvw->priv->auto_resize = auto_resize; +} + +gint64 +bacon_video_widget_get_current_time (BaconVideoWidget *bvw) +{ + int pos_time = 0, i = 0; + int pos_stream, length_time; + int status; + gboolean ret; + + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + status = xine_get_status (bvw->priv->stream); + if (status != XINE_STATUS_STOP && status != XINE_STATUS_PLAY) + return 0; + + ret = xine_get_pos_length (bvw->priv->stream, &pos_stream, + &pos_time, &length_time); + + while (ret == FALSE && i < 10) + { + usleep (100000); + ret = xine_get_pos_length (bvw->priv->stream, &pos_stream, + &pos_time, &length_time); + i++; + } + + if (bvw->priv->seeking == 1) + { + return bvw->priv->seek_dest * length_time; + } else if (bvw->priv->seeking == 2) { + return bvw->priv->seek_dest_time; + } + + if (ret == FALSE) + return -1; + + return pos_time; +} + +gint64 +bacon_video_widget_get_stream_length (BaconVideoWidget *bvw) +{ + int length_time = 0; + int pos_stream, pos_time; + + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (bvw->com->mrl == NULL) + return 0; + + xine_get_pos_length (bvw->priv->stream, &pos_stream, + &pos_time, &length_time); + + return length_time; +} + +gboolean +bacon_video_widget_is_playing (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (bvw->priv->stream == NULL) + return FALSE; + + return (xine_get_status (bvw->priv->stream) == XINE_STATUS_PLAY && xine_get_param (bvw->priv->stream, XINE_PARAM_SPEED) == XINE_SPEED_NORMAL); +} + +gboolean +bacon_video_widget_is_seekable (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (bvw->com->mrl == NULL) + return FALSE; + + if (bacon_video_widget_get_stream_length (bvw) == 0) + return FALSE; + + return xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_SEEKABLE); +} + +gboolean +bacon_video_widget_can_play (BaconVideoWidget *bvw, MediaType type) +{ + switch (type) + { + case MEDIA_TYPE_DVD: + return bvw->priv->can_dvd; + case MEDIA_TYPE_VCD: + return bvw->priv->can_vcd; + case MEDIA_TYPE_CDDA: + return bvw->priv->can_cdda; + default: + return FALSE; + } +} + +char +**bacon_video_widget_get_mrls (BaconVideoWidget *bvw, MediaType type) +{ + char *plugin_id; + int num_mrls; + + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + if (type == MEDIA_TYPE_DVD) + plugin_id = "DVD"; + else if (type == MEDIA_TYPE_VCD) + plugin_id = "VCD"; + else if (type == MEDIA_TYPE_CDDA) + plugin_id = "CD"; + else + return NULL; + + return g_strdupv (xine_get_autoplay_mrls + (bvw->priv->xine, plugin_id, &num_mrls)); +} + +void +bacon_video_widget_set_subtitle_font (BaconVideoWidget *bvw, const char *font) +{ + //FIXME +} + +void +bacon_video_widget_set_subtitle_encoding (BaconVideoWidget *bvw, const char *encoding) +{ + xine_cfg_entry_t entry; + char *lower; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (encoding != NULL); + + lower = g_ascii_strdown (encoding, -1); + + bvw_config_helper_string (bvw->priv->xine, + "subtitles.separate.src_encoding", lower, &entry); + entry.str_value = (char *) lower; + xine_config_update_entry (bvw->priv->xine, &entry); + g_free (lower); +} + +void +bacon_video_widget_set_video_device (BaconVideoWidget *bvw, const char *path) +{ + xine_cfg_entry_t entry; + + bvw_config_helper_string (bvw->priv->xine, + "media.video4linux.video_device", path, &entry); + entry.str_value = (char *) path; + xine_config_update_entry (bvw->priv->xine, &entry); +} + +void +bacon_video_widget_set_aspect_ratio (BaconVideoWidget *bvw, + BaconVideoWidgetAspectRatio ratio) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + xine_set_param (bvw->priv->stream, XINE_PARAM_VO_ASPECT_RATIO, ratio); +} + +BaconVideoWidgetAspectRatio +bacon_video_widget_get_aspect_ratio (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 0); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0); + g_return_val_if_fail (bvw->priv->xine != NULL, 0); + + return xine_get_param (bvw->priv->stream, XINE_PARAM_VO_ASPECT_RATIO); +} + +void +bacon_video_widget_set_scale_ratio (BaconVideoWidget *bvw, gfloat ratio) +{ + GtkWidget *toplevel, *widget; + int new_w, new_h, win_w, win_h; + + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (ratio >= 0); + + if (bvw->priv->fullscreen_mode != FALSE + || bvw->priv->logo_mode != FALSE) + return; + + /* Try best fit for the screen */ + if (ratio == 0) + { + if (totem_ratio_fits_screen (bvw->priv->video_window, bvw->priv->video_width, bvw->priv->video_height, 2) != FALSE) + { + ratio = 2; + } else if (totem_ratio_fits_screen (bvw->priv->video_window, bvw->priv->video_width, bvw->priv->video_height, 1) + != FALSE) { + ratio = 1; + } else if (totem_ratio_fits_screen (bvw->priv->video_window, bvw->priv->video_width, bvw->priv->video_height, 0.5) + != FALSE) { + ratio = 0.5; + } else { + return; + } + } else { + /* don't scale to something bigger than the screen, and leave + * us some room */ + if (totem_ratio_fits_screen (bvw->priv->video_window, bvw->priv->video_width, bvw->priv->video_height, ratio) == FALSE) + return; + } + + widget = GTK_WIDGET (bvw); + + toplevel = gtk_widget_get_toplevel (widget); + + /* Get the size of the toplevel window */ + gdk_drawable_get_size (GDK_DRAWABLE (toplevel->window), + &win_w, &win_h); + + /* Calculate the new size of the window, depending on the size of the + * video widget, and the new size of the video */ + new_w = win_w - widget->allocation.width + + bvw->priv->video_width * ratio; + new_h = win_h - widget->allocation.height + + bvw->priv->video_height * ratio; + + if (new_w == win_w && new_h == win_h) + return; + + /* Change the minimum size of the widget + * but only if we're getting a smaller window */ + if (new_w < widget->allocation.width + || new_h < widget->allocation.height) + { + gtk_widget_set_size_request (widget, + bvw->priv->video_width * ratio, + bvw->priv->video_height * ratio); + } + + gtk_window_resize (GTK_WINDOW (toplevel), 1, 1); + totem_widget_set_preferred_size (toplevel, new_w, new_h); +} + +gboolean +bacon_video_widget_can_set_zoom (BaconVideoWidget *bvw) +{ + return TRUE; +} + +void +bacon_video_widget_set_zoom (BaconVideoWidget *bvw, int zoom) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (zoom >= 0 && zoom <= 400); + + if (bvw->priv->stream == NULL) + return; + + xine_set_param (bvw->priv->stream, + XINE_PARAM_VO_ZOOM_X, zoom); + xine_set_param (bvw->priv->stream, + XINE_PARAM_VO_ZOOM_Y, zoom); +} + +int +bacon_video_widget_get_zoom (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, 100); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 100); + g_return_val_if_fail (bvw->priv->xine != NULL, 100); + + if (bvw->priv->stream == NULL) + return 100; + + return xine_get_param (bvw->priv->stream, XINE_PARAM_VO_ZOOM_X); +} + +int +bacon_video_widget_get_video_property (BaconVideoWidget *bvw, + BaconVideoWidgetVideoProperty type) +{ + g_return_val_if_fail (bvw != NULL, 65535 / 2); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 65535 / 2); + g_return_val_if_fail (bvw->priv->xine != NULL, 65535 / 2); + + return xine_get_param (bvw->priv->stream, video_props[type]); +} + +void +bacon_video_widget_set_video_property (BaconVideoWidget *bvw, + BaconVideoWidgetVideoProperty type, int value) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + + if ( !(value < 65535 && value > 0) ) + return; + + xine_set_param (bvw->priv->stream, video_props[type], value); + gconf_client_set_int (bvw->priv->gc, video_props_str[type], value, NULL); +} + +BaconVideoWidgetAudioOutType +bacon_video_widget_get_audio_out_type (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (bvw != NULL, BVW_AUDIO_SOUND_STEREO); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), + BVW_AUDIO_SOUND_STEREO); + g_return_val_if_fail (bvw->priv->xine != NULL, BVW_AUDIO_SOUND_STEREO); + + return gconf_client_get_int (bvw->priv->gc, + GCONF_PREFIX"/audio_output_type", NULL); +} + +gboolean +bacon_video_widget_set_audio_out_type (BaconVideoWidget *bvw, + BaconVideoWidgetAudioOutType type) +{ + xine_cfg_entry_t entry; + int value; + + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + if (type == bvw->priv->audio_out_type) + return FALSE; + bvw->priv->audio_out_type = type; + + xine_config_register_enum (bvw->priv->xine, + "audio.output.speaker_arrangement", + 1, + (char **) audio_out_types_strs, + "Speaker arrangement", + NULL, 0, NULL, NULL); + + gconf_client_set_int (bvw->priv->gc, + GCONF_PREFIX"/audio_output_type", + type, NULL); + + switch (type) { + case BVW_AUDIO_SOUND_STEREO: + value = 1; + break; + case BVW_AUDIO_SOUND_4CHANNEL: + value = 5; + break; + case BVW_AUDIO_SOUND_41CHANNEL: + value = 6; + break; + case BVW_AUDIO_SOUND_5CHANNEL: + value = 7; + break; + case BVW_AUDIO_SOUND_51CHANNEL: + value = 8; + break; + case BVW_AUDIO_SOUND_AC3PASSTHRU: + value = 12; + break; + default: + value = 1; + g_warning ("Unsupported audio type %d selected", type); + } + + xine_config_lookup_entry (bvw->priv->xine, + "audio.output.speaker_arrangement", &entry); + entry.num_value = value; + xine_config_update_entry (bvw->priv->xine, &entry); + + return FALSE; +} + +static void +bacon_video_widget_get_metadata_string (BaconVideoWidget *bvw, BaconVideoWidgetMetadataType type, + GValue *value) +{ + const char *string = NULL; + + g_value_init (value, G_TYPE_STRING); + + if (bvw->priv->stream == NULL) + { + g_value_set_string (value, string); + return; + } + + switch (type) + { + case BVW_INFO_TITLE: + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_TITLE); + break; + case BVW_INFO_ARTIST: + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_ARTIST); + break; + case BVW_INFO_ALBUM: + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_ALBUM); + break; + case BVW_INFO_YEAR: + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_YEAR); + break; + case BVW_INFO_VIDEO_CODEC: + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_VIDEOCODEC); + break; + case BVW_INFO_AUDIO_CODEC: + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_AUDIOCODEC); + break; + case BVW_INFO_AUDIO_CHANNELS: + { + //FIXME xine-lib sucks at this, it gives the + //mode of the output, not the one of the file +#if 0 + int mode; + + mode = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_AUDIO_MODE); + switch (mode) +#endif + } + break; + default: + g_assert_not_reached (); + } + + if (string != NULL && string[0] == '\0') + string = NULL; + + if (string != NULL) + { + if (g_utf8_validate (string, -1, NULL) == FALSE) + { + char *utf8; + + g_warning ("Metadata for index %d not in UTF-8 for mrl '%s'", type, bvw->com->mrl); + utf8 = g_locale_to_utf8 (string, -1, NULL, NULL, NULL); + g_value_set_string (value, utf8); + g_free (utf8); + return; + } + } + + g_value_set_string (value, string); + + return; +} + +static void +bacon_video_widget_get_metadata_int (BaconVideoWidget *bvw, + BaconVideoWidgetMetadataType type, GValue *value) +{ + int integer = 0; + + g_value_init (value, G_TYPE_INT); + + if (bvw->priv->stream == NULL) + { + g_value_set_int (value, 0); + return; + } + + switch (type) + { + case BVW_INFO_DURATION: + integer = bacon_video_widget_get_stream_length (bvw) / 1000; + break; + case BVW_INFO_DIMENSION_X: + integer = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_VIDEO_WIDTH); + break; + case BVW_INFO_DIMENSION_Y: + integer = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_VIDEO_HEIGHT); + break; + case BVW_INFO_FPS: + if (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_FRAME_DURATION) != 0) + { + integer = 90000 / xine_get_stream_info + (bvw->priv->stream, + XINE_STREAM_INFO_FRAME_DURATION); + } else { + integer = 0; + } + break; + case BVW_INFO_AUDIO_BITRATE: + integer = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_AUDIO_BITRATE) / 1000; + break; + case BVW_INFO_VIDEO_BITRATE: + integer = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_VIDEO_BITRATE) / 1000; + break; + case BVW_INFO_TRACK_NUMBER: + { + const char *string; + string = xine_get_meta_info (bvw->priv->stream, + XINE_META_INFO_TRACK_NUMBER); + if (string == NULL) + return; + + integer = (int) g_ascii_strtod (string, NULL); + } + break; + case BVW_INFO_AUDIO_SAMPLE_RATE: + integer = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_AUDIO_SAMPLERATE); + break; + default: + g_assert_not_reached (); + } + + g_value_set_int (value, integer); + + return; +} + +static void +bacon_video_widget_get_metadata_bool (BaconVideoWidget *bvw, + BaconVideoWidgetMetadataType type, GValue *value) +{ + gboolean boolean = FALSE; + + g_value_init (value, G_TYPE_BOOLEAN); + + if (bvw->priv->stream == NULL) + { + g_value_set_boolean (value, FALSE); + return; + } + + switch (type) + { + case BVW_INFO_HAS_VIDEO: + if (bvw->priv->logo_mode == FALSE) + boolean = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO); + break; + case BVW_INFO_HAS_AUDIO: + if (bvw->priv->logo_mode == FALSE) + boolean = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_HAS_AUDIO); + break; + default: + g_assert_not_reached (); + } + + g_value_set_boolean (value, boolean); + + return; +} + +void +bacon_video_widget_get_metadata (BaconVideoWidget *bvw, + BaconVideoWidgetMetadataType type, GValue *value) +{ + g_return_if_fail (bvw != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->xine != NULL); + g_return_if_fail (value != NULL); + + switch (type) + { + case BVW_INFO_TITLE: + case BVW_INFO_ARTIST: + case BVW_INFO_ALBUM: + case BVW_INFO_YEAR: + case BVW_INFO_VIDEO_CODEC: + case BVW_INFO_AUDIO_CODEC: + case BVW_INFO_AUDIO_CHANNELS: + bacon_video_widget_get_metadata_string (bvw, type, value); + break; + case BVW_INFO_DURATION: + case BVW_INFO_DIMENSION_X: + case BVW_INFO_DIMENSION_Y: + case BVW_INFO_FPS: + case BVW_INFO_AUDIO_BITRATE: + case BVW_INFO_VIDEO_BITRATE: + case BVW_INFO_TRACK_NUMBER: + case BVW_INFO_AUDIO_SAMPLE_RATE: + bacon_video_widget_get_metadata_int (bvw, type, value); + break; + case BVW_INFO_HAS_VIDEO: + case BVW_INFO_HAS_AUDIO: + bacon_video_widget_get_metadata_bool (bvw, type, value); + break; + default: + g_assert_not_reached (); + } + + return; +} + +GList +*bacon_video_widget_get_languages (BaconVideoWidget *bvw) +{ + GList *list = NULL; + int i, num_channels; + char lang[XINE_LANG_MAX]; + + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (bvw->priv->stream != NULL, NULL); + + if (bvw->com->mrl == NULL) + return NULL; + + num_channels = xine_get_stream_info + (bvw->priv->stream, XINE_STREAM_INFO_MAX_AUDIO_CHANNEL); + + if (num_channels < 2) + return NULL; + + for(i = 0; i < num_channels; i++) + { + memset (&lang, 0, sizeof (lang)); + + if (xine_get_audio_lang(bvw->priv->stream, i, lang) == 1) + { + char *nospace = lang; + + while (g_ascii_isspace (*nospace) != FALSE) + nospace++; + + list = g_list_prepend (list, + (gpointer) g_strdup (nospace)); + } else { + /* An unnamed language, for example 'Language 2' */ + list = g_list_prepend (list, + (gpointer) g_strdup_printf + (_("Language %d"), i + 1)); + } + } + + return g_list_reverse (list); +} + +int +bacon_video_widget_get_language (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1); + g_return_val_if_fail (bvw->priv->stream != NULL, -1); + + return xine_get_param (bvw->priv->stream, + XINE_PARAM_AUDIO_CHANNEL_LOGICAL); +} + +void +bacon_video_widget_set_language (BaconVideoWidget *bvw, int language) +{ + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->stream != NULL); + + xine_set_param (bvw->priv->stream, + XINE_PARAM_AUDIO_CHANNEL_LOGICAL, language); +} + +GList +*bacon_video_widget_get_subtitles (BaconVideoWidget *bvw) +{ + GList *list = NULL; + int i, num_channels; + char lang[XINE_LANG_MAX]; + + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (bvw->priv->stream != NULL, NULL); + + if (bvw->com->mrl == NULL) + return NULL; + + num_channels = xine_get_stream_info + (bvw->priv->stream, XINE_STREAM_INFO_MAX_SPU_CHANNEL); + + if (num_channels < 1) { + if (bvw->priv->has_subtitle != FALSE) { + return g_list_prepend (list, + (gpointer) g_strdup_printf + (_("Language %d"), 0)); + } + return NULL; + } + + for(i = 0; i < num_channels; i++) + { + memset (&lang, 0, sizeof (lang)); + + if (xine_get_spu_lang (bvw->priv->stream, i, lang) == 1) + { + char *nospace = lang; + + while (g_ascii_isspace (*nospace) != FALSE) + nospace++; + + list = g_list_prepend (list, + (gpointer) g_strdup (nospace)); + } else { + /* An unnamed language, for example 'Language 2' */ + list = g_list_prepend (list, + (gpointer) g_strdup_printf + (_("Language %d"), i + 1)); + } + } + + return g_list_reverse (list); +} + +int +bacon_video_widget_get_subtitle (BaconVideoWidget *bvw) +{ + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -2); + g_return_val_if_fail (bvw->priv->stream != NULL, -2); + + return xine_get_param (bvw->priv->stream, XINE_PARAM_SPU_CHANNEL); +} + +void +bacon_video_widget_set_subtitle (BaconVideoWidget *bvw, int subtitle) +{ + g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); + g_return_if_fail (bvw->priv->stream != NULL); + + xine_set_param (bvw->priv->stream, XINE_PARAM_SPU_CHANNEL, subtitle); +} + +gboolean +bacon_video_widget_has_next_track (BaconVideoWidget *bvw) +{ + int num, current; + + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), TRUE); + g_return_val_if_fail (bvw->priv->stream != NULL, TRUE); + + if (g_str_has_prefix (bvw->com->mrl, "dvd:") == FALSE + || bvw->com->mrl == NULL) + return TRUE; + + /* Check whether there's additional chapters first */ + num = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_DVD_CHAPTER_COUNT); + if (num == 0) + return FALSE; + + current = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_DVD_CHAPTER_NUMBER); + if (current < num) + return TRUE; + + /* And now with titles */ + num = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_DVD_TITLE_COUNT); + current = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_DVD_TITLE_NUMBER); + + return (current < num); +} + +gboolean +bacon_video_widget_has_previous_track (BaconVideoWidget *bvw) +{ + int current; + + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), TRUE); + g_return_val_if_fail (bvw->priv->stream != NULL, TRUE); + + if (g_str_has_prefix (bvw->com->mrl, "dvd:") == FALSE + || bvw->com->mrl == NULL) + return TRUE; + + current = xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_DVD_CHAPTER_NUMBER); + + /* We can't go back across titles */ + + return (current > 1); +} + +gboolean +bacon_video_widget_can_get_frames (BaconVideoWidget *bvw, GError **error) +{ + g_return_val_if_fail (bvw != NULL, FALSE); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE); + g_return_val_if_fail (bvw->priv->xine != NULL, FALSE); + + if (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_HAS_VIDEO) == FALSE + && bvw->priv->using_vfx == FALSE) + { + g_set_error (error, BVW_ERROR, BVW_ERROR_CANNOT_CAPTURE, + _("No video to capture.")); + return FALSE; + } + + if (xine_get_stream_info (bvw->priv->stream, + XINE_STREAM_INFO_VIDEO_HANDLED) == FALSE) + { + g_set_error (error, BVW_ERROR, BVW_ERROR_CANNOT_CAPTURE, + _("Video codec is not handled.")); + return FALSE; + } + + if (bvw->priv->type == BVW_USE_TYPE_CAPTURE) + return TRUE; + + if (xine_get_status (bvw->priv->stream) != XINE_STATUS_PLAY + && bvw->priv->logo_mode == FALSE) + { + g_set_error (error, BVW_ERROR, BVW_ERROR_CANNOT_CAPTURE, + _("Movie is not playing.")); + return FALSE; + } + + return TRUE; +} + +GdkPixbuf * +bacon_video_widget_get_current_frame (BaconVideoWidget *bvw) +{ + GdkPixbuf *pixbuf = NULL; + uint8_t *yuv, *y, *u, *v, *rgb; + int width, height, ratio, format; + xine_video_frame_t *frame = NULL; + + g_return_val_if_fail (bvw != NULL, NULL); + g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL); + g_return_val_if_fail (bvw->priv->xine != NULL, NULL); + + if (bvw->priv->type != BVW_USE_TYPE_CAPTURE) { + + if (xine_get_current_frame (bvw->priv->stream, &width, &height, + &ratio, &format, NULL) == 0) { + return NULL; + } + + if (width == 0 || height == 0) + return NULL; + + yuv = g_malloc ((width + 8) * (height + 1) * 2); + if (yuv == NULL) + return NULL; + + if (xine_get_current_frame (bvw->priv->stream, &width, &height, + &ratio, &format, yuv) == 0) + { + g_free (yuv); + return NULL; + } + } else { + frame = g_new0 (xine_video_frame_t, 1); + if (xine_get_next_video_frame (bvw->priv->vo_driver, frame) != 1) { + g_free (frame); + return NULL; + } + format = frame->colorspace; + width = frame->width; + height = frame->height; + yuv = frame->data; + ratio = frame->aspect_ratio; + } + + /* Convert to yv12 */ + switch (format) { + case XINE_IMGFMT_YUY2: + { + uint8_t *yuy2 = yuv; + + yuv = g_malloc (width * height * 2); + y = yuv; + u = yuv + width * height; + v = yuv + width * height * 5 / 4; + + yuy2toyv12 (y, u, v, yuy2, width, height); + + g_free (yuy2); + } + break; + case XINE_IMGFMT_YV12: + y = yuv; + u = yuv + width * height; + v = yuv + width * height * 5 / 4; + break; + default: + g_warning ("Format '%.4s' unsupported", (char *) &format); + g_free (yuv); + return NULL; + } + + switch (ratio) { + case XINE_VO_ASPECT_SQUARE: + ratio = 10000.0; + break; + case XINE_VO_ASPECT_4_3: + ratio = 10000.0 * 4 / 3; + break; + case XINE_VO_ASPECT_ANAMORPHIC: + ratio = 10000.0 * 16 / 9; + break; + case XINE_VO_ASPECT_DVB: + ratio = 10000.0 * 2.11; + break; + default: + ratio = 0.0; + } + + /* Convert to rgb */ + rgb = yv12torgb (y, u, v, width, height); + + pixbuf = gdk_pixbuf_new_from_data (rgb, + GDK_COLORSPACE_RGB, FALSE, + 8, width, height, 3 * width, + (GdkPixbufDestroyNotify) g_free, NULL); + + if (frame != NULL) { + xine_free_video_frame (bvw->priv->vo_driver, frame); + g_free (frame); + } + + if (ratio != 10000.0 && ratio != 0.0) + { + GdkPixbuf *tmp; + + if (ratio > 10000.0) + tmp = gdk_pixbuf_scale_simple (pixbuf, + (int) (height * ratio / 10000), height, + GDK_INTERP_BILINEAR); + else + tmp = gdk_pixbuf_scale_simple (pixbuf, + width, (int) (width * ratio / 10000), + GDK_INTERP_BILINEAR); + + gdk_pixbuf_unref (pixbuf); + + return tmp; + } + + return pixbuf; +} diff --git a/trunk/src/backend/bacon-video-widget.h b/trunk/src/backend/bacon-video-widget.h new file mode 100644 index 000000000..6751e2817 --- /dev/null +++ b/trunk/src/backend/bacon-video-widget.h @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2001,2002,2003,2004,2005 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef HAVE_BACON_VIDEO_WIDGET_H +#define HAVE_BACON_VIDEO_WIDGET_H + +#include <gtk/gtkbox.h> + +/* for optical disc enumeration type */ +#include "totem-disc.h" + +G_BEGIN_DECLS + +#define BACON_VIDEO_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), bacon_video_widget_get_type (), BaconVideoWidget)) +#define BACON_VIDEO_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), bacon_video_widget_get_type (), BaconVideoWidgetClass)) +#define BACON_IS_VIDEO_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, bacon_video_widget_get_type ())) +#define BACON_IS_VIDEO_WIDGET_CLASS(klass) (G_CHECK_INSTANCE_GET_CLASS ((klass), bacon_video_widget_get_type ())) +#define BVW_ERROR bacon_video_widget_error_quark () + +typedef struct BaconVideoWidgetPrivate BaconVideoWidgetPrivate; +typedef struct BaconVideoWidgetCommon BaconVideoWidgetCommon; + +typedef struct { + GtkBox parent; + BaconVideoWidgetCommon *com; + BaconVideoWidgetPrivate *priv; +} BaconVideoWidget; + +typedef struct { + GtkBoxClass parent_class; + + void (*error) (GtkWidget *bvw, const char *message, + gboolean playback_stopped, gboolean fatal); + void (*eos) (GtkWidget *bvw); + void (*got_metadata) (GtkWidget *bvw); + void (*got_redirect) (GtkWidget *bvw, const char *mrl); + void (*title_change) (GtkWidget *bvw, const char *title); + void (*channels_change) (GtkWidget *bvw); + void (*tick) (GtkWidget *bvw, gint64 current_time, gint64 stream_length, + float current_position, gboolean seekable); + void (*buffering) (GtkWidget *bvw, guint progress); +} BaconVideoWidgetClass; + +typedef enum { + /* Plugins */ + BVW_ERROR_AUDIO_PLUGIN, + BVW_ERROR_NO_PLUGIN_FOR_FILE, + BVW_ERROR_VIDEO_PLUGIN, + BVW_ERROR_AUDIO_BUSY, + /* File */ + BVW_ERROR_BROKEN_FILE, + BVW_ERROR_FILE_GENERIC, + BVW_ERROR_FILE_PERMISSION, + BVW_ERROR_FILE_ENCRYPTED, + BVW_ERROR_FILE_NOT_FOUND, + /* Devices */ + BVW_ERROR_DVD_ENCRYPTED, + BVW_ERROR_INVALID_DEVICE, + /* Network */ + BVW_ERROR_UNKNOWN_HOST, + BVW_ERROR_NETWORK_UNREACHABLE, + BVW_ERROR_CONNECTION_REFUSED, + /* Generic */ + BVW_ERROR_UNVALID_LOCATION, + BVW_ERROR_GENERIC, + BVW_ERROR_CODEC_NOT_HANDLED, + BVW_ERROR_AUDIO_ONLY, + BVW_ERROR_CANNOT_CAPTURE, + BVW_ERROR_READ_ERROR, + BVW_ERROR_PLUGIN_LOAD, + BVW_ERROR_EMPTY_FILE +} BvwError; + +GQuark bacon_video_widget_error_quark (void) G_GNUC_CONST; +GType bacon_video_widget_get_type (void); +GOptionGroup* bacon_video_widget_get_option_group (void); +/* This can be used if the app does not use popt */ +void bacon_video_widget_init_backend (int *argc, char ***argv); + +typedef enum { + BVW_USE_TYPE_VIDEO, + BVW_USE_TYPE_AUDIO, + BVW_USE_TYPE_CAPTURE, + BVW_USE_TYPE_METADATA +} BvwUseType; + +GtkWidget *bacon_video_widget_new (int width, int height, + BvwUseType type, + GError **error); + +char *bacon_video_widget_get_backend_name (BaconVideoWidget *bvw); + +/* Actions */ +#define bacon_video_widget_open(bvw, mrl, error) bacon_video_widget_open_with_subtitle(bvw, mrl, NULL, error) +gboolean bacon_video_widget_open_with_subtitle (BaconVideoWidget *bvw, + const char *mrl, + const char *subtitle_uri, + GError **error); +gboolean bacon_video_widget_play (BaconVideoWidget *bvw, + GError **error); +void bacon_video_widget_pause (BaconVideoWidget *bvw); +gboolean bacon_video_widget_is_playing (BaconVideoWidget *bvw); + +/* Seeking and length */ +gboolean bacon_video_widget_is_seekable (BaconVideoWidget *bvw); +gboolean bacon_video_widget_seek (BaconVideoWidget *bvw, + float position, + GError **error); +gboolean bacon_video_widget_seek_time (BaconVideoWidget *bvw, + gint64 time, + GError **error); +gboolean bacon_video_widget_can_direct_seek (BaconVideoWidget *bvw); +float bacon_video_widget_get_position (BaconVideoWidget *bvw); +gint64 bacon_video_widget_get_current_time (BaconVideoWidget *bvw); +gint64 bacon_video_widget_get_stream_length (BaconVideoWidget *bvw); + +void bacon_video_widget_stop (BaconVideoWidget *bvw); +void bacon_video_widget_close (BaconVideoWidget *bvw); + +/* Audio volume */ +gboolean bacon_video_widget_can_set_volume (BaconVideoWidget *bvw); +void bacon_video_widget_set_volume (BaconVideoWidget *bvw, + int volume); +int bacon_video_widget_get_volume (BaconVideoWidget *bvw); + +/* Properties */ +void bacon_video_widget_set_logo (BaconVideoWidget *bvw, + char *filename); +void bacon_video_widget_set_logo_pixbuf (BaconVideoWidget *bvw, + GdkPixbuf *logo); +void bacon_video_widget_set_logo_mode (BaconVideoWidget *bvw, + gboolean logo_mode); +gboolean bacon_video_widget_get_logo_mode (BaconVideoWidget *bvw); + +void bacon_video_widget_set_fullscreen (BaconVideoWidget *bvw, + gboolean fullscreen); + +void bacon_video_widget_set_show_cursor (BaconVideoWidget *bvw, + gboolean use_cursor); +gboolean bacon_video_widget_get_show_cursor (BaconVideoWidget *bvw); + +gboolean bacon_video_widget_get_auto_resize (BaconVideoWidget *bvw); +void bacon_video_widget_set_auto_resize (BaconVideoWidget *bvw, + gboolean auto_resize); + +void bacon_video_widget_set_connection_speed (BaconVideoWidget *bvw, + int speed); +int bacon_video_widget_get_connection_speed (BaconVideoWidget *bvw); + +void bacon_video_widget_set_media_device (BaconVideoWidget *bvw, + const char *path); +gboolean bacon_video_widget_can_play (BaconVideoWidget *bvw, + MediaType type); +gchar **bacon_video_widget_get_mrls (BaconVideoWidget *bvw, + MediaType type); +void bacon_video_widget_set_subtitle_font (BaconVideoWidget *bvw, + const char *font); +void bacon_video_widget_set_subtitle_encoding (BaconVideoWidget *bvw, + const char *encoding); + +/* Video devices */ +void bacon_video_widget_set_video_device (BaconVideoWidget *bvw, + const char *path); + +/* Metadata */ +typedef enum { + BVW_INFO_TITLE, + BVW_INFO_ARTIST, + BVW_INFO_YEAR, + BVW_INFO_ALBUM, + BVW_INFO_DURATION, + BVW_INFO_TRACK_NUMBER, + /* Video */ + BVW_INFO_HAS_VIDEO, + BVW_INFO_DIMENSION_X, + BVW_INFO_DIMENSION_Y, + BVW_INFO_VIDEO_BITRATE, + BVW_INFO_VIDEO_CODEC, + BVW_INFO_FPS, + /* Audio */ + BVW_INFO_HAS_AUDIO, + BVW_INFO_AUDIO_BITRATE, + BVW_INFO_AUDIO_CODEC, + BVW_INFO_AUDIO_SAMPLE_RATE, + BVW_INFO_AUDIO_CHANNELS +} BaconVideoWidgetMetadataType; + +void bacon_video_widget_get_metadata (BaconVideoWidget *bvw, + BaconVideoWidgetMetadataType + type, + GValue *value); + +/* Visualisation functions */ +typedef enum { + VISUAL_SMALL, + VISUAL_NORMAL, + VISUAL_LARGE, + VISUAL_EXTRA_LARGE, + NUM_VISUAL_QUALITIES +} VisualsQuality; + +gboolean bacon_video_widget_set_show_visuals (BaconVideoWidget *bvw, + gboolean show_visuals); +GList *bacon_video_widget_get_visuals_list (BaconVideoWidget *bvw); +gboolean bacon_video_widget_set_visuals (BaconVideoWidget *bvw, + const char *name); +void bacon_video_widget_set_visuals_quality (BaconVideoWidget *bvw, + VisualsQuality quality); + +/* Picture settings */ +typedef enum { + BVW_VIDEO_BRIGHTNESS, + BVW_VIDEO_CONTRAST, + BVW_VIDEO_SATURATION, + BVW_VIDEO_HUE +} BaconVideoWidgetVideoProperty; + +typedef enum { + BVW_RATIO_AUTO, + BVW_RATIO_SQUARE, + BVW_RATIO_FOURBYTHREE, + BVW_RATIO_ANAMORPHIC, + BVW_RATIO_DVB +} BaconVideoWidgetAspectRatio; + +void bacon_video_widget_set_deinterlacing (BaconVideoWidget *bvw, + gboolean deinterlace); +gboolean bacon_video_widget_get_deinterlacing (BaconVideoWidget *bvw); + +void bacon_video_widget_set_aspect_ratio (BaconVideoWidget *bvw, + BaconVideoWidgetAspectRatio + ratio); +BaconVideoWidgetAspectRatio bacon_video_widget_get_aspect_ratio + (BaconVideoWidget *bvw); + +void bacon_video_widget_set_scale_ratio (BaconVideoWidget *bvw, + float ratio); + +gboolean bacon_video_widget_can_set_zoom (BaconVideoWidget *bvw); +void bacon_video_widget_set_zoom (BaconVideoWidget *bvw, + int zoom); +int bacon_video_widget_get_zoom (BaconVideoWidget *bvw); + +int bacon_video_widget_get_video_property (BaconVideoWidget *bvw, + BaconVideoWidgetVideoProperty + type); +void bacon_video_widget_set_video_property (BaconVideoWidget *bvw, + BaconVideoWidgetVideoProperty + type, + int value); + +/* DVD functions */ +typedef enum { + BVW_DVD_ROOT_MENU, + BVW_DVD_TITLE_MENU, + BVW_DVD_SUBPICTURE_MENU, + BVW_DVD_AUDIO_MENU, + BVW_DVD_ANGLE_MENU, + BVW_DVD_CHAPTER_MENU, + BVW_DVD_NEXT_CHAPTER, + BVW_DVD_PREV_CHAPTER, + BVW_DVD_NEXT_TITLE, + BVW_DVD_PREV_TITLE, + BVW_DVD_NEXT_ANGLE, + BVW_DVD_PREV_ANGLE, + BVW_DVD_ROOT_MENU_UP, + BVW_DVD_ROOT_MENU_DOWN, + BVW_DVD_ROOT_MENU_LEFT, + BVW_DVD_ROOT_MENU_RIGHT, + BVW_DVD_ROOT_MENU_SELECT +} BaconVideoWidgetDVDEvent; + +void bacon_video_widget_dvd_event (BaconVideoWidget *bvw, + BaconVideoWidgetDVDEvent + type); +GList *bacon_video_widget_get_languages (BaconVideoWidget *bvw); +int bacon_video_widget_get_language (BaconVideoWidget *bvw); +void bacon_video_widget_set_language (BaconVideoWidget *bvw, + int language); + +GList *bacon_video_widget_get_subtitles (BaconVideoWidget *bvw); +int bacon_video_widget_get_subtitle (BaconVideoWidget *bvw); +void bacon_video_widget_set_subtitle (BaconVideoWidget *bvw, + int subtitle); + +gboolean bacon_video_widget_has_next_track (BaconVideoWidget *bvw); +gboolean bacon_video_widget_has_previous_track (BaconVideoWidget *bvw); + +/* Screenshot functions */ +gboolean bacon_video_widget_can_get_frames (BaconVideoWidget *bvw, + GError **error); +GdkPixbuf *bacon_video_widget_get_current_frame (BaconVideoWidget *bvw); + +/* TV-Out functions */ +typedef enum { + TV_OUT_NONE, + TV_OUT_NVTV_PAL, + TV_OUT_NVTV_NTSC +} TvOutType; + +gboolean bacon_video_widget_fullscreen_mode_available (BaconVideoWidget *bvw, + TvOutType tvout); + +void bacon_video_widget_set_tv_out (BaconVideoWidget *bvw, + TvOutType tvout); +TvOutType bacon_video_widget_get_tv_out (BaconVideoWidget *bvw); + +/* Audio-out functions */ +typedef enum { + BVW_AUDIO_SOUND_STEREO, + BVW_AUDIO_SOUND_4CHANNEL, + BVW_AUDIO_SOUND_41CHANNEL, + BVW_AUDIO_SOUND_5CHANNEL, + BVW_AUDIO_SOUND_51CHANNEL, + BVW_AUDIO_SOUND_AC3PASSTHRU +} BaconVideoWidgetAudioOutType; + +BaconVideoWidgetAudioOutType bacon_video_widget_get_audio_out_type + (BaconVideoWidget *bvw); +gboolean bacon_video_widget_set_audio_out_type (BaconVideoWidget *bvw, + BaconVideoWidgetAudioOutType + type); + +G_END_DECLS + +#endif /* HAVE_BACON_VIDEO_WIDGET_H */ diff --git a/trunk/src/backend/baconvideowidget-marshal.list b/trunk/src/backend/baconvideowidget-marshal.list new file mode 100644 index 000000000..39a7cd2aa --- /dev/null +++ b/trunk/src/backend/baconvideowidget-marshal.list @@ -0,0 +1,3 @@ +VOID:INT64,INT64,FLOAT,BOOLEAN +VOID:STRING,BOOLEAN,BOOLEAN +BOOLEAN:BOXED,BOXED,BOOLEAN diff --git a/trunk/src/backend/bvw-test.c b/trunk/src/backend/bvw-test.c new file mode 100644 index 000000000..56d715107 --- /dev/null +++ b/trunk/src/backend/bvw-test.c @@ -0,0 +1,93 @@ + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include "bacon-video-widget.h" +#ifdef GDK_WINDOWING_X11 +#include <X11/Xlib.h> +#endif + +static GtkWidget *win; +static GtkWidget *bvw; +static char *mrl, *argument; + +static void +test_bvw_set_mrl (char *path) +{ + mrl = g_strdup (path); + bacon_video_widget_open (BACON_VIDEO_WIDGET (bvw), mrl, NULL); +} + +static void +on_redirect (GtkWidget *bvw, const char *mrl, gpointer data) +{ + g_message ("Redirect to: %s", mrl); +} + +static void +on_eos_event (GtkWidget *widget, gpointer user_data) +{ + bacon_video_widget_stop (BACON_VIDEO_WIDGET (bvw)); + bacon_video_widget_close (BACON_VIDEO_WIDGET (bvw)); + g_free (mrl); + + test_bvw_set_mrl (argument); + + bacon_video_widget_play (BACON_VIDEO_WIDGET (bvw), NULL); +} + +static void +error_cb (GtkWidget *bvw, const char *message, + gboolean playback_stopped, gboolean fatal) +{ + g_message ("Error: %s, playback stopped: %d, fatal: %d", + message, playback_stopped, fatal); +} + +int main +(int argc, char **argv) +{ + guint32 height = 500; + guint32 width = 500; + + if (argc > 2) { + g_warning ("Usage: %s <file>", argv[0]); + return 1; + } + +#ifdef GDK_WINDOWING_X11 + XInitThreads (); +#endif + g_thread_init (NULL); + gtk_init (&argc, &argv); + bacon_video_widget_init_backend (NULL, NULL); + gdk_threads_init (); + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (win), width, height); + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (gtk_main_quit), NULL); + + bvw = bacon_video_widget_new (width, height, + BVW_USE_TYPE_VIDEO, NULL); + + g_signal_connect (G_OBJECT (bvw),"eos",G_CALLBACK (on_eos_event),NULL); + g_signal_connect (G_OBJECT (bvw), "got-redirect", G_CALLBACK (on_redirect), NULL); + g_signal_connect (G_OBJECT (bvw), "error", G_CALLBACK (error_cb), NULL); + + gtk_container_add (GTK_CONTAINER (win),bvw); + + gtk_widget_realize (GTK_WIDGET (win)); + gtk_widget_realize (bvw); + + gtk_widget_show (win); + gtk_widget_show (bvw); + + mrl = NULL; + test_bvw_set_mrl (argv[1] ? argv[1] : LOGO_PATH); + argument = g_strdup (argv[1] ? argv[1] : LOGO_PATH); + bacon_video_widget_play (BACON_VIDEO_WIDGET (bvw), NULL); + gtk_main (); + + return 0; +} + diff --git a/trunk/src/backend/debug.h b/trunk/src/backend/debug.h new file mode 100644 index 000000000..26fb16856 --- /dev/null +++ b/trunk/src/backend/debug.h @@ -0,0 +1,38 @@ +#ifndef __TOTEM_DEBUG_H__ +#define __TOTEM_DEBUG_H__ 1 + +#ifdef GNOME_ENABLE_DEBUG + +#include <sys/time.h> + +#ifdef G_HAVE_ISO_VARARGS +#define D(...) g_message (__VA_ARGS__) +#else +#define D(x...) g_message (x) +#endif +#define TE() { g_message ("enter %s", __PRETTY_FUNCTION__ ); gdk_threads_enter (); } +#define TL() { g_message ("leave %s", __PRETTY_FUNCTION__); gdk_threads_leave (); } +#define TOTEM_PROFILE(function) \ + do{ \ + struct timeval current_time; \ + double dtime; \ + gettimeofday(¤t_time, NULL); \ + dtime = -(current_time.tv_sec + (current_time.tv_usec / 1000000.0)); \ + function; \ + gettimeofday(¤t_time, NULL); \ + dtime += current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ + printf("(%s:%d) took %lf seconds\n", \ + __PRETTY_FUNCTION__, __LINE__, dtime ); \ + }while(0) +#else +#ifdef G_HAVE_ISO_VARARGS +#define D(...) +#else +#define D(x...) +#endif +#define TE() { gdk_threads_enter (); } +#define TL() { gdk_threads_leave (); } +#define TOTEM_PROFILE(function) function +#endif + +#endif /* __TOTEM_DEBUG_H__ */ diff --git a/trunk/src/backend/gstscreenshot.c b/trunk/src/backend/gstscreenshot.c new file mode 100644 index 000000000..f0ea90756 --- /dev/null +++ b/trunk/src/backend/gstscreenshot.c @@ -0,0 +1,199 @@ +/* Small helper element for format conversion + * (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <string.h> + +#include "gstscreenshot.h" + +GST_DEBUG_CATEGORY_EXTERN (_totem_gst_debug_cat); +#define GST_CAT_DEFAULT _totem_gst_debug_cat + +static void +feed_fakesrc (GstElement * src, GstBuffer * buf, GstPad * pad, gpointer data) +{ + GstBuffer *in_buf = GST_BUFFER (data); + + g_assert (GST_BUFFER_SIZE (buf) >= GST_BUFFER_SIZE (in_buf)); + g_assert (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_READONLY)); + + gst_buffer_set_caps (buf, GST_BUFFER_CAPS (in_buf)); + + memcpy (GST_BUFFER_DATA (buf), GST_BUFFER_DATA (in_buf), + GST_BUFFER_SIZE (in_buf)); + + GST_BUFFER_SIZE (buf) = GST_BUFFER_SIZE (in_buf); + + GST_DEBUG ("feeding buffer %p, size %u, caps %" GST_PTR_FORMAT, + buf, GST_BUFFER_SIZE (buf), GST_BUFFER_CAPS (buf)); +} + +static void +save_result (GstElement * sink, GstBuffer * buf, GstPad * pad, gpointer data) +{ + GstBuffer **p_buf = (GstBuffer **) data; + + *p_buf = gst_buffer_ref (buf); + + GST_DEBUG ("received converted buffer %p with caps %" GST_PTR_FORMAT, + *p_buf, GST_BUFFER_CAPS (*p_buf)); +} + +static gboolean +create_element (const gchar *factory_name, GstElement **element, GError **err) +{ + *element = gst_element_factory_make (factory_name, NULL); + if (*element) + return TRUE; + + if (err && *err == NULL) { + *err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "cannot create element '%s' - please check your GStreamer installation", + factory_name); + } + + return FALSE; +} + +/* takes ownership of the input buffer */ +GstBuffer * +bvw_frame_conv_convert (GstBuffer * buf, GstCaps * to_caps) +{ + GstElement *src, *csp, *filter1, *vscale, *filter2, *sink, *pipeline; + GstMessage *msg; + GstBuffer *result = NULL; + GError *error = NULL; + GstBus *bus; + GstCaps *to_caps_no_par; + + g_return_val_if_fail (GST_BUFFER_CAPS (buf) != NULL, NULL); + + /* videoscale is here to correct for the pixel-aspect-ratio for us */ + GST_DEBUG ("creating elements"); + if (!create_element ("fakesrc", &src, &error) || + !create_element ("ffmpegcolorspace", &csp, &error) || + !create_element ("videoscale", &vscale, &error) || + !create_element ("capsfilter", &filter1, &error) || + !create_element ("capsfilter", &filter2, &error) || + !create_element ("fakesink", &sink, &error)) { + g_warning ("Could not take screenshot: %s", error->message); + g_error_free (error); + return NULL; + } + + pipeline = gst_pipeline_new ("screenshot-pipeline"); + if (pipeline == NULL) { + g_warning ("Could not take screenshot: %s", "no pipeline (unknown error)"); + return NULL; + } + + GST_DEBUG ("adding elements"); + gst_bin_add_many (GST_BIN (pipeline), src, csp, filter1, vscale, filter2, + sink, NULL); + + g_signal_connect (src, "handoff", G_CALLBACK (feed_fakesrc), buf); + + /* set to 'fixed' sizetype */ + g_object_set (src, "sizemax", GST_BUFFER_SIZE (buf), "sizetype", 2, + "num-buffers", 1, "signal-handoffs", TRUE, NULL); + + /* adding this superfluous capsfilter makes linking cheaper */ + to_caps_no_par = gst_caps_copy (to_caps); + gst_structure_remove_field (gst_caps_get_structure (to_caps_no_par, 0), + "pixel-aspect-ratio"); + g_object_set (filter1, "caps", to_caps_no_par, NULL); + gst_caps_unref (to_caps_no_par); + + g_object_set (filter2, "caps", to_caps, NULL); + + g_signal_connect (sink, "handoff", G_CALLBACK (save_result), &result); + + g_object_set (sink, "preroll-queue-len", 1, "signal-handoffs", TRUE, NULL); + + /* FIXME: linking is still way too expensive, profile this properly */ + GST_DEBUG ("linking src->csp"); + if (!gst_element_link_pads (src, "src", csp, "sink")) + return NULL; + + GST_DEBUG ("linking csp->filter1"); + if (!gst_element_link_pads (csp, "src", filter1, "sink")) + return NULL; + + GST_DEBUG ("linking filter1->vscale"); + if (!gst_element_link_pads (filter1, "src", vscale, "sink")) + return NULL; + + GST_DEBUG ("linking vscale->capsfilter"); + if (!gst_element_link_pads (vscale, "src", filter2, "sink")) + return NULL; + + GST_DEBUG ("linking capsfilter->sink"); + if (!gst_element_link_pads (filter2, "src", sink, "sink")) + return NULL; + + GST_DEBUG ("running conversion pipeline"); + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + bus = gst_element_get_bus (pipeline); + msg = gst_bus_poll (bus, GST_MESSAGE_ERROR | GST_MESSAGE_EOS, 25*GST_SECOND); + + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS: { + if (result) { + GST_DEBUG ("conversion successful: result = %p", result); + } else { + GST_WARNING ("EOS but no result frame?!"); + } + break; + } + case GST_MESSAGE_ERROR: { + gchar *dbg = NULL; + + gst_message_parse_error (msg, &error, &dbg); + if (error) { + g_warning ("Could not take screenshot: %s", error->message); + GST_DEBUG ("%s [debug: %s]", error->message, GST_STR_NULL (dbg)); + g_error_free (error); + } else { + g_warning ("Could not take screenshot (and NULL error!)"); + } + g_free (dbg); + result = NULL; + break; + } + default: { + g_return_val_if_reached (NULL); + } + } + } else { + g_warning ("Could not take screenshot: %s", "timeout during conversion"); + result = NULL; + } + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + return result; +} + diff --git a/trunk/src/backend/gstscreenshot.h b/trunk/src/backend/gstscreenshot.h new file mode 100644 index 000000000..ea79455dd --- /dev/null +++ b/trunk/src/backend/gstscreenshot.h @@ -0,0 +1,32 @@ +/* Small helper element for format conversion + * (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __BVW_FRAME_CONV_H__ +#define __BVW_FRAME_CONV_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +GstBuffer * bvw_frame_conv_convert (GstBuffer *buf, + GstCaps *to); + +G_END_DECLS + +#endif /* __BVW_FRAME_CONV_H__ */ diff --git a/trunk/src/backend/video-utils.c b/trunk/src/backend/video-utils.c new file mode 100644 index 000000000..85a9b9de9 --- /dev/null +++ b/trunk/src/backend/video-utils.c @@ -0,0 +1,374 @@ + +#include "config.h" + +#include "video-utils.h" + +#include <glib/gi18n.h> +#include <libintl.h> + +#include <gdk/gdk.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +/* Code taken from: + * transcode Copyright (C) Thomas Oestreich - June 2001 + * enix enix.berlios.de + */ + +void yuy2toyv12 (guint8 *y, guint8 *u, guint8 *v, guint8 *input, + int width, int height) { + int i, j, w2; + + w2 = width / 2; + + for (i = 0; i < height; i += 2) { + for (j = 0; j < w2; j++) { + /* packed YUV 422 is: Y[i] U[i] Y[i+1] V[i] */ + *(y++) = *(input++); + *(u++) = *(input++); + *(y++) = *(input++); + *(v++) = *(input++); + } + + /* down sampling */ + for (j = 0; j < w2; j++) { + /* skip every second line for U and V */ + *(y++) = *(input++); + input++; + *(y++) = *(input++); + input++; + } + } +} + +#define clip_8_bit(val) \ +{ \ + if (val < 0) \ + val = 0; \ + else \ + if (val > 255) val = 255; \ +} + +guint8 * yv12torgb (guint8 *src_y, guint8 *src_u, guint8 *src_v, + int width, int height) { + int i, j; + + int y, u, v; + int r, g, b; + + int sub_i_uv; + int sub_j_uv; + + int uv_width, uv_height; + + guchar *rgb; + + uv_width = width / 2; + uv_height = height / 2; + + rgb = (guchar *) malloc (width * height * 3); + if (!rgb) + return NULL; + + for (i = 0; i < height; ++i) { + /* calculate u & v rows */ + sub_i_uv = ((i * uv_height) / height); + + for (j = 0; j < width; ++j) { + /* calculate u & v columns */ + sub_j_uv = ((j * uv_width) / width); + + /*************************************************** + * Colour conversion from + * http://www.inforamp.net/~poynton/notes/colour_and_gamma/ColorFAQ.html#RTFToC30 + * + * Thanks to Billy Biggs <vektor@dumbterm.net> + * for the pointer and the following conversion. + * + * R' = [ 1.1644 0 1.5960 ] ([ Y' ] [ 16 ]) + * G' = [ 1.1644 -0.3918 -0.8130 ] * ([ Cb ] - [ 128 ]) + * B' = [ 1.1644 2.0172 0 ] ([ Cr ] [ 128 ]) + * + * Where in xine the above values are represented as + * Y' == image->y + * Cb == image->u + * Cr == image->v + * + ***************************************************/ + + y = src_y[(i * width) + j] - 16; + u = src_u[(sub_i_uv * uv_width) + sub_j_uv] - 128; + v = src_v[(sub_i_uv * uv_width) + sub_j_uv] - 128; + + r = (1.1644 * y) + (1.5960 * v); + g = (1.1644 * y) - (0.3918 * u) - (0.8130 * v); + b = (1.1644 * y) + (2.0172 * u); + + clip_8_bit (r); + clip_8_bit (g); + clip_8_bit (b); + + rgb[(i * width + j) * 3 + 0] = r; + rgb[(i * width + j) * 3 + 1] = g; + rgb[(i * width + j) * 3 + 2] = b; + } + } + + return rgb; +} + +void +totem_gdk_window_set_invisible_cursor (GdkWindow *window) +{ + GdkBitmap *empty_bitmap; + GdkCursor *cursor; + GdkColor useless; + char invisible_cursor_bits[] = { 0x0 }; + + useless.red = useless.green = useless.blue = 0; + useless.pixel = 0; + + empty_bitmap = gdk_bitmap_create_from_data (window, + invisible_cursor_bits, + 1, 1); + + /* When there's no window, there's no bitmap */ + if (empty_bitmap == NULL) + return; + + cursor = gdk_cursor_new_from_pixmap (empty_bitmap, + empty_bitmap, + &useless, + &useless, 0, 0); + + gdk_window_set_cursor (window, cursor); + + gdk_cursor_unref (cursor); + + g_object_unref (empty_bitmap); +} + +void +totem_gdk_window_set_waiting_cursor (GdkWindow *window) +{ + GdkCursor *cursor; + + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + + gdk_flush (); +} + +gboolean +totem_display_is_local (void) +{ + const char *name, *work; + int display, screen; + gboolean has_hostname; + + name = gdk_display_get_name (gdk_display_get_default ()); + if (name == NULL) + return TRUE; + + work = strstr (name, ":"); + if (work == NULL) + return TRUE; + + has_hostname = (work - name) > 0; + + /* Get to the character after the colon */ + work++; + if (work == NULL) + return TRUE; + + if (sscanf (work, "%d.%d", &display, &screen) != 2) + return TRUE; + + if (has_hostname == FALSE) + return TRUE; + + if (display < 10) + return TRUE; + + return FALSE; +} + +char * +totem_time_to_string (gint64 msecs) +{ + int sec, min, hour, time; + + time = (int) (msecs / 1000); + sec = time % 60; + time = time - sec; + min = (time % (60*60)) / 60; + time = time - (min * 60); + hour = time / (60*60); + + if (hour > 0) + { + /* hour:minutes:seconds */ + /* Translators: This is a time format, like "9:05:02" for 9 + * hours, 5 minutes, and 2 seconds. You may change ":" to + * the separator that your locale uses or use "%Id" instead + * of "%d" if your locale uses localized digits. Do not + * translate the "long time format|" part. Remove it from + * the translation. + */ + return g_strdup_printf (Q_("long time format|%d:%02d:%02d"), hour, min, sec); + } else { + /* minutes:seconds */ + /* Translators: This is a time format, like "5:02" for 5 + * minutes and 2 seconds. You may change ":" to the + * separator that your locale uses or use "%Id" instead of + * "%d" if your locale uses localized digits. Do not + * translate the "short time format|" part. Remove it from + * the translation. + */ + return g_strdup_printf (Q_("short time format|%d:%02d"), min, sec); + } + + return NULL; +} + +char * +totem_time_to_string_text (gint64 msecs) +{ + char *secs, *mins, *hours, *string; + int sec, min, hour, time; + + time = (int) (msecs / 1000); + sec = time % 60; + time = time - sec; + min = (time % (60*60)) / 60; + time = time - (min * 60); + hour = time / (60*60); + + hours = g_strdup_printf (ngettext ("%d hour", "%d hours", hour), hour); + + mins = g_strdup_printf (ngettext ("%d minute", + "%d minutes", min), min); + + secs = g_strdup_printf (ngettext ("%d second", + "%d seconds", sec), sec); + + if (hour > 0) + { + /* hour:minutes:seconds */ + string = g_strdup_printf (_("%s %s %s"), hours, mins, secs); + } else if (min > 0) { + /* minutes:seconds */ + string = g_strdup_printf (_("%s %s"), mins, secs); + } else if (sec > 0) { + /* seconds */ + string = g_strdup_printf (_("%s"), secs); + } else { + /* 0 seconds */ + string = g_strdup (_("0 seconds")); + } + + g_free (hours); + g_free (mins); + g_free (secs); + + return string; +} + +typedef struct _TotemPrefSize { + gint width, height; + gulong sig_id; +} TotemPrefSize; + +static gboolean +cb_unset_size (gpointer data) +{ + GtkWidget *widget = data; + + gtk_widget_queue_resize_no_redraw (widget); + + return FALSE; +} + +static void +cb_set_preferred_size (GtkWidget *widget, GtkRequisition *req, + gpointer data) +{ + TotemPrefSize *size = data; + + req->width = size->width; + req->height = size->height; + + g_signal_handler_disconnect (widget, size->sig_id); + g_free (size); + g_idle_add (cb_unset_size, widget); +} + +void +totem_widget_set_preferred_size (GtkWidget *widget, gint width, + gint height) +{ + TotemPrefSize *size = g_new (TotemPrefSize, 1); + + size->width = width; + size->height = height; + size->sig_id = g_signal_connect (widget, "size-request", + G_CALLBACK (cb_set_preferred_size), + size); + + gtk_widget_queue_resize (widget); +} + +gboolean +totem_ratio_fits_screen (GdkWindow *video_window, int video_width, + int video_height, gfloat ratio) +{ + GdkRectangle fullscreen_rect; + int new_w, new_h; + GdkScreen *screen; + + if (video_width <= 0 || video_height <= 0) + return TRUE; + + new_w = video_width * ratio; + new_h = video_height * ratio; + + screen = gdk_drawable_get_screen (GDK_DRAWABLE (video_window)); + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window + (screen, video_window), + &fullscreen_rect); + + if (new_w > (fullscreen_rect.width - 128) || + new_h > (fullscreen_rect.height - 128)) + { + return FALSE; + } + + return TRUE; +} + +char * +totem_resolve_relative_link (const char *old_mrl, const char *new_mrl) +{ + char *ret, *tmp, *l; + + g_return_val_if_fail (new_mrl != NULL, NULL); + g_return_val_if_fail (old_mrl != NULL, NULL); + + /* if new mrl is already absolute, just return it as it is */ + if (strstr (new_mrl, "://") != NULL) + return g_strdup (new_mrl); + + tmp = g_strdup (old_mrl); + if ((l = strrchr (tmp, '/'))) + *l = '\0'; + + ret = g_strconcat (tmp, "/", new_mrl, NULL); + g_free (tmp); + + return ret; +} + diff --git a/trunk/src/backend/video-utils.h b/trunk/src/backend/video-utils.h new file mode 100644 index 000000000..65a99fbae --- /dev/null +++ b/trunk/src/backend/video-utils.h @@ -0,0 +1,25 @@ + +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#define TOTEM_OBJECT_HAS_SIGNAL(obj, name) (g_signal_lookup (name, g_type_from_name (G_OBJECT_TYPE_NAME (obj))) != 0) + +void totem_gdk_window_set_invisible_cursor (GdkWindow *window); +void totem_gdk_window_set_waiting_cursor (GdkWindow *window); + +void yuy2toyv12 (guint8 *y, guint8 *u, guint8 *v, guint8 *input, + int width, int height); +guint8 *yv12torgb (guint8 *src_y, guint8 *src_u, guint8 *src_v, + int width, int height); + +gboolean totem_display_is_local (void); + +char *totem_time_to_string (gint64 msecs); +char *totem_time_to_string_text (gint64 msecs); + +void totem_widget_set_preferred_size (GtkWidget *widget, gint width, + gint height); +gboolean totem_ratio_fits_screen (GdkWindow *window, int video_width, + int video_height, gfloat ratio); +char *totem_resolve_relative_link (const char *old_mrl, const char *new_mrl); + diff --git a/trunk/src/bacon-message-connection.c b/trunk/src/bacon-message-connection.c new file mode 100644 index 000000000..c8000de24 --- /dev/null +++ b/trunk/src/bacon-message-connection.c @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#include "bacon-message-connection.h" + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +struct BaconMessageConnection { + /* A server accepts connections */ + gboolean is_server; + + /* The socket path itself */ + char *path; + + /* File descriptor of the socket */ + int fd; + /* Channel to watch */ + GIOChannel *chan; + /* Event id returned by g_io_add_watch() */ + int conn_id; + + /* Connections accepted by this connection */ + GSList *accepted_connections; + + /* callback */ + void (*func) (const char *message, gpointer user_data); + gpointer data; +}; + +static gboolean +test_is_socket (const char *path) +{ + struct stat s; + + if (stat (path, &s) == -1) + return FALSE; + + if (S_ISSOCK (s.st_mode)) + return TRUE; + + return FALSE; +} + +static gboolean +is_owned_by_user_and_socket (const char *path) +{ + struct stat s; + + if (stat (path, &s) == -1) + return FALSE; + + if (s.st_uid != geteuid ()) + return FALSE; + + if ((s.st_mode & S_IFSOCK) != S_IFSOCK) + return FALSE; + + return TRUE; +} + +static gboolean server_cb (GIOChannel *source, + GIOCondition condition, gpointer data); + +static gboolean +setup_connection (BaconMessageConnection *conn) +{ + g_return_val_if_fail (conn->chan == NULL, FALSE); + + conn->chan = g_io_channel_unix_new (conn->fd); + if (!conn->chan) { + return FALSE; + } + g_io_channel_set_line_term (conn->chan, "\n", 1); + conn->conn_id = g_io_add_watch (conn->chan, G_IO_IN, server_cb, conn); + + return TRUE; +} + +static void +accept_new_connection (BaconMessageConnection *server_conn) +{ + BaconMessageConnection *conn; + int alen; + + g_return_if_fail (server_conn->is_server); + + conn = g_new0 (BaconMessageConnection, 1); + conn->is_server = FALSE; + conn->func = server_conn->func; + conn->data = server_conn->data; + + conn->fd = accept (server_conn->fd, NULL, (guint *)&alen); + + server_conn->accepted_connections = + g_slist_prepend (server_conn->accepted_connections, conn); + + setup_connection (conn); +} + +static gboolean +server_cb (GIOChannel *source, GIOCondition condition, gpointer data) +{ + BaconMessageConnection *conn = (BaconMessageConnection *)data; + char *message, *subs, buf; + int cd, rc, offset; + gboolean finished; + + offset = 0; + if (conn->is_server && conn->fd == g_io_channel_unix_get_fd (source)) { + accept_new_connection (conn); + return TRUE; + } + message = g_malloc (1); + cd = conn->fd; + rc = read (cd, &buf, 1); + while (rc > 0 && buf != '\n') + { + message = g_realloc (message, rc + offset + 1); + message[offset] = buf; + offset = offset + rc; + rc = read (cd, &buf, 1); + } + if (rc <= 0) { + g_io_channel_shutdown (conn->chan, FALSE, NULL); + g_io_channel_unref (conn->chan); + conn->chan = NULL; + close (conn->fd); + conn->fd = -1; + g_free (message); + conn->conn_id = 0; + + return FALSE; + } + message[offset] = '\0'; + + subs = message; + finished = FALSE; + + while (finished == FALSE && *subs != '\0') + { + if (conn->func != NULL) + (*conn->func) (subs, conn->data); + + subs += strlen (subs) + 1; + if (subs - message >= offset) + finished = TRUE; + } + + g_free (message); + + return TRUE; +} + +static char * +find_file_with_pattern (const char *dir, const char *pattern) +{ + GDir *filedir; + char *found_filename; + const char *filename; + GPatternSpec *pat; + + filedir = g_dir_open (dir, 0, NULL); + if (filedir == NULL) + return NULL; + + pat = g_pattern_spec_new (pattern); + if (pat == NULL) + { + g_dir_close (filedir); + return NULL; + } + + found_filename = NULL; + + while ((filename = g_dir_read_name (filedir))) + { + if (g_pattern_match_string (pat, filename)) + { + char *tmp = g_build_filename (dir, filename, NULL); + if (is_owned_by_user_and_socket (tmp)) + found_filename = g_strdup (filename); + g_free (tmp); + } + + if (found_filename != NULL) + break; + } + + g_pattern_spec_free (pat); + g_dir_close (filedir); + + return found_filename; +} + +static char * +socket_filename (const char *prefix) +{ + char *pattern, *newfile, *path, *filename; + const char *tmpdir; + + pattern = g_strdup_printf ("%s.%s.*", prefix, g_get_user_name ()); + tmpdir = g_get_tmp_dir (); + filename = find_file_with_pattern (tmpdir, pattern); + if (filename == NULL) + { + newfile = g_strdup_printf ("%s.%s.%u", prefix, + g_get_user_name (), g_random_int ()); + path = g_build_filename (tmpdir, newfile, NULL); + g_free (newfile); + } else { + path = g_build_filename (tmpdir, filename, NULL); + g_free (filename); + } + + g_free (pattern); + return path; +} + +static gboolean +try_server (BaconMessageConnection *conn) +{ + struct sockaddr_un uaddr; + + uaddr.sun_family = AF_UNIX; + strncpy (uaddr.sun_path, conn->path, + MIN (strlen(conn->path)+1, UNIX_PATH_MAX)); + conn->fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (bind (conn->fd, (struct sockaddr *) &uaddr, sizeof (uaddr)) == -1) + { + conn->fd = -1; + return FALSE; + } + listen (conn->fd, 5); + + if (!setup_connection (conn)) + return FALSE; + return TRUE; +} + +static gboolean +try_client (BaconMessageConnection *conn) +{ + struct sockaddr_un uaddr; + + uaddr.sun_family = AF_UNIX; + strncpy (uaddr.sun_path, conn->path, + MIN(strlen(conn->path)+1, UNIX_PATH_MAX)); + conn->fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (connect (conn->fd, (struct sockaddr *) &uaddr, + sizeof (uaddr)) == -1) + { + conn->fd = -1; + return FALSE; + } + + return setup_connection (conn); +} + +BaconMessageConnection * +bacon_message_connection_new (const char *prefix) +{ + BaconMessageConnection *conn; + + g_return_val_if_fail (prefix != NULL, NULL); + + conn = g_new0 (BaconMessageConnection, 1); + conn->path = socket_filename (prefix); + + if (test_is_socket (conn->path) == FALSE) + { + if (!try_server (conn)) + { + bacon_message_connection_free (conn); + return NULL; + } + + conn->is_server = TRUE; + return conn; + } + + if (try_client (conn) == FALSE) + { + unlink (conn->path); + try_server (conn); + if (conn->fd == -1) + { + bacon_message_connection_free (conn); + return NULL; + } + + conn->is_server = TRUE; + return conn; + } + + conn->is_server = FALSE; + return conn; +} + +void +bacon_message_connection_free (BaconMessageConnection *conn) +{ + GSList *child_conn; + + g_return_if_fail (conn != NULL); + /* Only servers can accept other connections */ + g_return_if_fail (conn->is_server != FALSE || + conn->accepted_connections == NULL); + + child_conn = conn->accepted_connections; + while (child_conn != NULL) { + bacon_message_connection_free (child_conn->data); + child_conn = g_slist_next (child_conn); + } + g_slist_free (conn->accepted_connections); + + if (conn->conn_id) { + g_source_remove (conn->conn_id); + conn->conn_id = 0; + } + if (conn->chan) { + g_io_channel_shutdown (conn->chan, FALSE, NULL); + g_io_channel_unref (conn->chan); + } + + if (conn->is_server != FALSE) { + unlink (conn->path); + } + if (conn->fd != -1) { + close (conn->fd); + } + + g_free (conn->path); + g_free (conn); +} + +void +bacon_message_connection_set_callback (BaconMessageConnection *conn, + BaconMessageReceivedFunc func, + gpointer user_data) +{ + g_return_if_fail (conn != NULL); + + conn->func = func; + conn->data = user_data; +} + +void +bacon_message_connection_send (BaconMessageConnection *conn, + const char *message) +{ + g_return_if_fail (conn != NULL); + g_return_if_fail (message != NULL); + + g_io_channel_write_chars (conn->chan, message, strlen (message), + NULL, NULL); + g_io_channel_write_chars (conn->chan, "\n", 1, NULL, NULL); + g_io_channel_flush (conn->chan, NULL); +} + +gboolean +bacon_message_connection_get_is_server (BaconMessageConnection *conn) +{ + g_return_val_if_fail (conn != NULL, FALSE); + + return conn->is_server; +} + diff --git a/trunk/src/bacon-message-connection.h b/trunk/src/bacon-message-connection.h new file mode 100644 index 000000000..aac7a2d11 --- /dev/null +++ b/trunk/src/bacon-message-connection.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef BACON_MESSAGE_CONNECTION_H +#define BACON_MESSAGE_CONNECTION_H + +#include <glib.h> + +G_BEGIN_DECLS + +typedef void (*BaconMessageReceivedFunc) (const char *message, + gpointer user_data); + +typedef struct BaconMessageConnection BaconMessageConnection; + +BaconMessageConnection *bacon_message_connection_new (const char *prefix); +void bacon_message_connection_free (BaconMessageConnection *conn); +void bacon_message_connection_set_callback (BaconMessageConnection *conn, + BaconMessageReceivedFunc func, + gpointer user_data); +void bacon_message_connection_send (BaconMessageConnection *conn, + const char *message); +gboolean bacon_message_connection_get_is_server (BaconMessageConnection *conn); + +G_END_DECLS + +#endif /* BACON_MESSAGE_CONNECTION_H */ diff --git a/trunk/src/bacon-v4l-selection.c b/trunk/src/bacon-v4l-selection.c new file mode 100644 index 000000000..59801ecf9 --- /dev/null +++ b/trunk/src/bacon-v4l-selection.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2002 Bastien Nocera <hadess@hadess.net> + * + * bacon-v4l-selection.c + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Bastien Nocera <hadess@hadess.net> + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib.h> +#include <glib/gi18n.h> + +#include <string.h> + +#include <gtk/gtkmenu.h> +#include <gtk/gtkcombobox.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkcelllayout.h> +#include <gtk/gtkcellrenderertext.h> + +#include "bacon-v4l-selection.h" +#include "video-dev.h" + +/* Signals */ +enum { + DEVICE_CHANGED, + LAST_SIGNAL +}; + +/* Arguments */ +enum { + PROP_0, + PROP_DEVICE, +}; + +struct BaconV4lSelectionPrivate { + GList *cdroms; +}; + +static void bacon_v4l_selection_init (BaconV4lSelection *bvs); + +static void bacon_v4l_selection_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec); +static void bacon_v4l_selection_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec); + +static void bacon_v4l_selection_finalize (GObject *object); + +static GtkWidgetClass *parent_class = NULL; + +static int bvs_table_signals[LAST_SIGNAL] = { 0 }; + +static VideoDev * +get_video_device (BaconV4lSelection *bvs, int nr) +{ + GList *item; + + item = g_list_nth (bvs->priv->cdroms, nr); + if (item == NULL) + return NULL; + else + return item->data; +} + +G_DEFINE_TYPE(BaconV4lSelection, bacon_v4l_selection, GTK_TYPE_COMBO_BOX) + +static void +bacon_v4l_selection_class_init (BaconV4lSelectionClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + + parent_class = gtk_type_class (gtk_combo_box_get_type ()); + + /* GObject */ + object_class->set_property = bacon_v4l_selection_set_property; + object_class->get_property = bacon_v4l_selection_get_property; + object_class->finalize = bacon_v4l_selection_finalize; + + /* Properties */ + g_object_class_install_property (object_class, PROP_DEVICE, + g_param_spec_string ("device", NULL, NULL, + FALSE, G_PARAM_READWRITE)); + + /* Signals */ + bvs_table_signals[DEVICE_CHANGED] = + g_signal_new ("device-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconV4lSelectionClass, + device_changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +bacon_v4l_selection_init (BaconV4lSelection *bvs) +{ + bvs->priv = g_new0 (BaconV4lSelectionPrivate, 1); +} + +static void +bacon_v4l_selection_finalize (GObject *object) +{ + GList *l; + BaconV4lSelection *bvs = (BaconV4lSelection *) object; + + g_return_if_fail (bvs != NULL); + g_return_if_fail (BACON_IS_V4L_SELECTION (bvs)); + + l = bvs->priv->cdroms; + while (l != NULL) + { + VideoDev *cdrom = l->data; + + l = g_list_remove (l, cdrom); + video_dev_free (cdrom); + } + + g_free (bvs->priv); + bvs->priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize != NULL) { + (* G_OBJECT_CLASS (parent_class)->finalize) (object); + } +} + +static void +combo_device_changed (GtkComboBox *combo, gpointer user_data) +{ + BaconV4lSelection *bvs = (BaconV4lSelection *) user_data; + VideoDev *drive; + int i; + + i = gtk_combo_box_get_active (combo); + drive = get_video_device (bvs, i); + + g_signal_emit (G_OBJECT (bvs), + bvs_table_signals[DEVICE_CHANGED], + 0, drive->device); +} + +static void +cdrom_combo_box (BaconV4lSelection *bvs) +{ + GList *l; + VideoDev *cdrom; + + bvs->priv->cdroms = scan_for_video_devices (); + + for (l = bvs->priv->cdroms; l != NULL; l = l->next) + { + cdrom = l->data; + + if (cdrom->display_name == NULL) { + g_warning ("cdrom->display_name != NULL failed"); + } + + gtk_combo_box_append_text (GTK_COMBO_BOX (bvs), + cdrom->display_name + ? cdrom->display_name : _("Unnamed CDROM")); + } + gtk_combo_box_set_active (GTK_COMBO_BOX (bvs), 0); + + if (bvs->priv->cdroms == NULL) { + gtk_widget_set_sensitive (GTK_WIDGET (bvs), FALSE); + } +} + +GtkWidget * +bacon_v4l_selection_new (void) +{ + GtkWidget *widget; + BaconV4lSelection *bvs; + GtkCellRenderer *cell; + GtkListStore *store; + + widget = GTK_WIDGET + (g_object_new (bacon_v4l_selection_get_type (), NULL)); + + store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_combo_box_set_model (GTK_COMBO_BOX (widget), + GTK_TREE_MODEL (store)); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell, + "text", 0, + NULL); + + bvs = BACON_V4L_SELECTION (widget); + cdrom_combo_box (bvs); + + g_signal_connect (G_OBJECT (bvs), "changed", + G_CALLBACK (combo_device_changed), bvs); + + return widget; +} + +/* Properties */ +static void +bacon_v4l_selection_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + BaconV4lSelection *bvs; + + g_return_if_fail (BACON_IS_V4L_SELECTION (object)); + + bvs = BACON_V4L_SELECTION (object); + + switch (property_id) + { + case PROP_DEVICE: + bacon_v4l_selection_set_device (bvs, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +bacon_v4l_selection_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + BaconV4lSelection *bvs; + + g_return_if_fail (BACON_IS_V4L_SELECTION (object)); + + bvs = BACON_V4L_SELECTION (object); + + switch (property_id) + { + case PROP_DEVICE: + g_value_set_string (value, bacon_v4l_selection_get_device (bvs)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +const char * +bacon_v4l_selection_get_default_device (BaconV4lSelection *bvs) +{ + GList *l; + VideoDev *drive; + + g_return_val_if_fail (bvs != NULL, "/dev/video0"); + g_return_val_if_fail (BACON_IS_V4L_SELECTION (bvs), "/dev/video0"); + + l = bvs->priv->cdroms; + if (bvs->priv->cdroms == NULL) + return "/dev/video0"; + + drive = l->data; + + return drive->device; +} + +void +bacon_v4l_selection_set_device (BaconV4lSelection *bvs, const char *device) +{ + GList *l; + VideoDev *drive; + gboolean found; + int i; + + found = FALSE; + i = -1; + + g_return_if_fail (bvs != NULL); + g_return_if_fail (BACON_IS_V4L_SELECTION (bvs)); + + for (l = bvs->priv->cdroms; l != NULL && found == FALSE; + l = l->next) + { + i++; + + drive = l->data; + + if (strcmp (drive->device, device) == 0) + found = TRUE; + } + + if (found) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (bvs), i); + } else { + /* If the device doesn't exist, set it back to + * the default */ + gtk_combo_box_set_active (GTK_COMBO_BOX (bvs), 0); + + drive = get_video_device (bvs, 0); + + if (drive == NULL) + return; + + g_signal_emit (G_OBJECT (bvs), + bvs_table_signals [DEVICE_CHANGED], + 0, drive->device); + } +} + +const char * +bacon_v4l_selection_get_device (BaconV4lSelection *bvs) +{ + VideoDev *drive; + int i; + + g_return_val_if_fail (bvs != NULL, NULL); + g_return_val_if_fail (BACON_IS_V4L_SELECTION (bvs), NULL); + + i = gtk_combo_box_get_active (GTK_COMBO_BOX (bvs)); + drive = get_video_device (bvs, i); + + return drive ? drive->device : NULL; +} + diff --git a/trunk/src/bacon-v4l-selection.h b/trunk/src/bacon-v4l-selection.h new file mode 100644 index 000000000..da2e51f89 --- /dev/null +++ b/trunk/src/bacon-v4l-selection.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2002 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Bastien Nocera <hadess@hadess.net> + * + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef HAVE_BACON_V4L_SELECTION_H +#define HAVE_BACON_V4L_SELECTION_H + +#include <gtk/gtkcomboboxentry.h> + +G_BEGIN_DECLS + +#define BACON_V4L_SELECTION(obj) (GTK_CHECK_CAST ((obj), bacon_v4l_selection_get_type (), BaconV4lSelection)) +#define BACON_V4L_SELECTION_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), bacon_v4l_selection_get_type (), BaconV4lSelectionClass)) +#define BACON_IS_V4L_SELECTION(obj) (GTK_CHECK_TYPE (obj, bacon_v4l_selection_get_type ())) +#define BACON_IS_V4L_SELECTION_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), bacon_v4l_selection_get_type ())) + +typedef struct BaconV4lSelectionPrivate BaconV4lSelectionPrivate; + +typedef struct { + GtkComboBox widget; + BaconV4lSelectionPrivate *priv; +} BaconV4lSelection; + +typedef struct { + GtkComboBoxClass parent_class; + void (*device_changed) (GtkWidget *bvs, const char *device_path); +} BaconV4lSelectionClass; + +GtkType bacon_v4l_selection_get_type (void); +GtkWidget *bacon_v4l_selection_new (void); + +void bacon_v4l_selection_set_device (BaconV4lSelection *bvs, + const char *device); +const char *bacon_v4l_selection_get_device (BaconV4lSelection *bvs); +const char *bacon_v4l_selection_get_default_device (BaconV4lSelection *bvs); + +G_END_DECLS + +#endif /* HAVE_BACON_V4L_SELECTION_H */ diff --git a/trunk/src/bacon-video-widget-properties.c b/trunk/src/bacon-video-widget-properties.c new file mode 100644 index 000000000..c0631e981 --- /dev/null +++ b/trunk/src/bacon-video-widget-properties.c @@ -0,0 +1,328 @@ +/* bacon-video-widget-properties.c + + Copyright (C) 2002 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" +#include "bacon-video-widget-properties.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glade/glade.h> +#include <string.h> +#include "video-utils.h" +#include "totem-interface.h" + +#include "debug.h" + +/* used in bacon_video_widget_properties_update() */ +#define UPDATE_FROM_STRING(type, name) \ + do { \ + const char *temp; \ + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), \ + type, &value); \ + if ((temp = g_value_get_string (&value)) != NULL) { \ + bacon_video_widget_properties_set_label (props, name, \ + temp); \ + } \ + g_value_unset (&value); \ + } while (0) + +#define UPDATE_FROM_INT(type, name, format, empty) \ + do { \ + char *temp; \ + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), \ + type, &value); \ + if (g_value_get_int (&value) != 0) \ + temp = g_strdup_printf (gettext (format), \ + g_value_get_int (&value)); \ + else \ + temp = g_strdup (empty); \ + bacon_video_widget_properties_set_label (props, name, temp); \ + g_free (temp); \ + g_value_unset (&value); \ + } while (0) + +#define UPDATE_FROM_INT2(type1, type2, name, format) \ + do { \ + int x, y; \ + char *temp; \ + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), \ + type1, &value); \ + x = g_value_get_int (&value); \ + g_value_unset (&value); \ + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), \ + type2, &value); \ + y = g_value_get_int (&value); \ + g_value_unset (&value); \ + temp = g_strdup_printf (gettext (format), x, y); \ + bacon_video_widget_properties_set_label (props, name, temp); \ + g_free (temp); \ + } while (0) + +struct BaconVideoWidgetPropertiesPrivate +{ + GladeXML *xml; + int time; +}; + +static GtkWidgetClass *parent_class = NULL; + +static void bacon_video_widget_properties_class_init + (BaconVideoWidgetPropertiesClass *class); +static void bacon_video_widget_properties_init + (BaconVideoWidgetProperties *props); + +G_DEFINE_TYPE(BaconVideoWidgetProperties, bacon_video_widget_properties, GTK_TYPE_VBOX) + +static void +bacon_video_widget_properties_init (BaconVideoWidgetProperties *props) +{ + props->priv = g_new0 (BaconVideoWidgetPropertiesPrivate, 1); +} + +static void +bacon_video_widget_properties_finalize (GObject *object) +{ + BaconVideoWidgetProperties *props = BACON_VIDEO_WIDGET_PROPERTIES (object); + + g_return_if_fail (object != NULL); + + g_object_unref (props->priv->xml); + g_free (props->priv); + + if (G_OBJECT_CLASS (parent_class)->finalize != NULL) { + (* G_OBJECT_CLASS (parent_class)->finalize) (object); + } +} + +static void +bacon_video_widget_properties_set_label (BaconVideoWidgetProperties *props, + const char *name, const char *text) +{ + GtkWidget *item; + + item = glade_xml_get_widget (props->priv->xml, name); + gtk_label_set_text (GTK_LABEL (item), text); +} + +void +bacon_video_widget_properties_reset (BaconVideoWidgetProperties *props) +{ + GtkWidget *item; + + g_return_if_fail (props != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET_PROPERTIES (props)); + + item = glade_xml_get_widget (props->priv->xml, "video_vbox"); + gtk_widget_show (item); + item = glade_xml_get_widget (props->priv->xml, "video"); + gtk_widget_set_sensitive (item, FALSE); + item = glade_xml_get_widget (props->priv->xml, "audio"); + gtk_widget_set_sensitive (item, FALSE); + + /* Title */ + bacon_video_widget_properties_set_label (props, "title", _("Unknown")); + /* Artist */ + bacon_video_widget_properties_set_label (props, "artist", _("Unknown")); + /* Album */ + bacon_video_widget_properties_set_label (props, "album", _("Unknown")); + /* Year */ + bacon_video_widget_properties_set_label (props, "year", _("Unknown")); + /* Duration */ + bacon_video_widget_properties_from_time (props, 0); + + /* Dimensions */ + bacon_video_widget_properties_set_label (props, "dimensions", _("N/A")); + /* Video Codec */ + bacon_video_widget_properties_set_label (props, "vcodec", _("N/A")); + /* Video Bitrate */ + bacon_video_widget_properties_set_label (props, "video_bitrate", + _("N/A")); + /* Framerate */ + bacon_video_widget_properties_set_label (props, "framerate", + _("N/A")); + /* Audio Bitrate */ + bacon_video_widget_properties_set_label (props, "audio_bitrate", + _("N/A")); + /* Audio Codec */ + bacon_video_widget_properties_set_label (props, "acodec", _("N/A")); + /* Sample rate */ + bacon_video_widget_properties_set_label (props, "samplerate", _("0 Hz")); + /* Channels */ + bacon_video_widget_properties_set_label (props, "channels", _("0 Channels")); +} + +void +bacon_video_widget_properties_from_time (BaconVideoWidgetProperties *props, + int time) +{ + char *string; + + g_return_if_fail (props != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET_PROPERTIES (props)); + + if (time == props->priv->time) + return; + + string = totem_time_to_string_text (time); + bacon_video_widget_properties_set_label (props, "duration", string); + g_free (string); + + props->priv->time = time; +} + +void +bacon_video_widget_properties_update (BaconVideoWidgetProperties *props, + BaconVideoWidget *bvw) +{ + GtkWidget *item; + GValue value = { 0, }; + gboolean has_type; + + g_return_if_fail (props != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET_PROPERTIES (props)); + g_return_if_fail (bvw != NULL); + + /* General */ + UPDATE_FROM_STRING (BVW_INFO_TITLE, "title"); + UPDATE_FROM_STRING (BVW_INFO_ARTIST, "artist"); + UPDATE_FROM_STRING (BVW_INFO_ALBUM, "album"); + UPDATE_FROM_STRING (BVW_INFO_YEAR, "year"); + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_DURATION, &value); + bacon_video_widget_properties_from_time (props, + g_value_get_int (&value) * 1000); + g_value_unset (&value); + + /* Video */ + item = glade_xml_get_widget (props->priv->xml, "video"); + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_HAS_VIDEO, &value); + has_type = g_value_get_boolean (&value); + gtk_widget_set_sensitive (item, has_type); + g_value_unset (&value); + + item = glade_xml_get_widget (props->priv->xml, "video_vbox"); + + if (has_type != FALSE) + { + UPDATE_FROM_INT2 (BVW_INFO_DIMENSION_X, BVW_INFO_DIMENSION_Y, + "dimensions", N_("%d x %d")); + UPDATE_FROM_STRING (BVW_INFO_VIDEO_CODEC, "vcodec"); + UPDATE_FROM_INT (BVW_INFO_FPS, "framerate", + N_("%d frames per second"), _("N/A")); + UPDATE_FROM_INT (BVW_INFO_VIDEO_BITRATE, "video_bitrate", + N_("%d kbps"), _("N/A")); + gtk_widget_show (item); + } else { + gtk_widget_hide (item); + } + + /* Audio */ + item = glade_xml_get_widget (props->priv->xml, "audio"); + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_HAS_AUDIO, &value); + has_type = g_value_get_boolean (&value); + gtk_widget_set_sensitive (item, has_type); + g_value_unset (&value); + + if (has_type != FALSE) + { + UPDATE_FROM_INT (BVW_INFO_AUDIO_BITRATE, "audio_bitrate", + N_("%d kbps"), _("N/A")); + UPDATE_FROM_STRING (BVW_INFO_AUDIO_CODEC, "acodec"); + UPDATE_FROM_INT (BVW_INFO_AUDIO_SAMPLE_RATE, "samplerate", + N_("%d Hz"), _("N/A")); + UPDATE_FROM_STRING (BVW_INFO_AUDIO_CHANNELS, "channels"); + } + +#undef UPDATE_FROM_STRING +#undef UPDATE_FROM_INT +#undef UPDATE_FROM_INT2 +} + +void +bacon_video_widget_properties_from_metadata (BaconVideoWidgetProperties *props, + const char *title, + const char *artist, + const char *album) +{ + g_return_if_fail (props != NULL); + g_return_if_fail (BACON_IS_VIDEO_WIDGET_PROPERTIES (props)); + g_return_if_fail (title != NULL); + g_return_if_fail (artist != NULL); + g_return_if_fail (album != NULL); + + bacon_video_widget_properties_set_label (props, "title", title); + bacon_video_widget_properties_set_label (props, "artist", artist); + bacon_video_widget_properties_set_label (props, "album", album); +} + +GtkWidget* +bacon_video_widget_properties_new (void) +{ + BaconVideoWidgetProperties *props; + GladeXML *xml; + GtkWidget *vbox; + GtkSizeGroup *group; + const char *labels[] = { "title_label", "artist_label", "album_label", + "year_label", "duration_label", "dimensions_label", "vcodec_label", + "framerate_label", "vbitrate_label", "abitrate_label", + "acodec_label", "samplerate_label", "channels_label" }; + guint i; + + xml = totem_interface_load_with_root ("properties.glade", + "vbox1", _("Properties dialog"), TRUE, NULL); + + if (xml == NULL) + return NULL; + + props = BACON_VIDEO_WIDGET_PROPERTIES (g_object_new + (BACON_TYPE_VIDEO_WIDGET_PROPERTIES, NULL)); + + props->priv->xml = xml; + vbox = glade_xml_get_widget (props->priv->xml, "vbox1"); + gtk_box_pack_start (GTK_BOX (props), vbox, FALSE, FALSE, 0); + + bacon_video_widget_properties_reset (props); + + group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + for (i = 0; i < G_N_ELEMENTS (labels); i++) + gtk_size_group_add_widget (group, glade_xml_get_widget (xml, + labels[i])); + + g_object_unref (group); + + gtk_widget_show_all (GTK_WIDGET (props)); + + return GTK_WIDGET (props); +} + +static void +bacon_video_widget_properties_class_init (BaconVideoWidgetPropertiesClass *klass) +{ + parent_class = gtk_type_class (gtk_vbox_get_type ()); + + G_OBJECT_CLASS (klass)->finalize = bacon_video_widget_properties_finalize; +} + diff --git a/trunk/src/bacon-video-widget-properties.h b/trunk/src/bacon-video-widget-properties.h new file mode 100644 index 000000000..09bd27d6f --- /dev/null +++ b/trunk/src/bacon-video-widget-properties.h @@ -0,0 +1,63 @@ +/* bacon-video-widget-properties.h: Properties dialog for BaconVideoWidget + + Copyright (C) 2002 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef BACON_VIDEO_WIDGET_PROPERTIES_H +#define BACON_VIDEO_WIDGET_PROPERTIES_H + +#include <gtk/gtkvbox.h> +#include "bacon-video-widget.h" + +#define BACON_TYPE_VIDEO_WIDGET_PROPERTIES (bacon_video_widget_properties_get_type ()) +#define BACON_VIDEO_WIDGET_PROPERTIES(obj) (GTK_CHECK_CAST ((obj), BACON_TYPE_VIDEO_WIDGET_PROPERTIES, BaconVideoWidgetProperties)) +#define BACON_VIDEO_WIDGET_PROPERTIES_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), BACON_TYPE_VIDEO_WIDGET_PROPERTIES, BaconVideoWidgetPropertiesClass)) +#define BACON_IS_VIDEO_WIDGET_PROPERTIES(obj) (GTK_CHECK_TYPE ((obj), BACON_TYPE_VIDEO_WIDGET_PROPERTIES)) +#define BACON_IS_VIDEO_WIDGET_PROPERTIES_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), BACON_TYPE_VIDEO_WIDGET_PROPERTIES)) + +typedef struct BaconVideoWidgetProperties BaconVideoWidgetProperties; +typedef struct BaconVideoWidgetPropertiesClass BaconVideoWidgetPropertiesClass; +typedef struct BaconVideoWidgetPropertiesPrivate BaconVideoWidgetPropertiesPrivate; + +struct BaconVideoWidgetProperties { + GtkVBox parent; + BaconVideoWidgetPropertiesPrivate *priv; +}; + +struct BaconVideoWidgetPropertiesClass { + GtkVBoxClass parent_class; +}; + +GtkType bacon_video_widget_properties_get_type (void); +GtkWidget *bacon_video_widget_properties_new (void); + +void bacon_video_widget_properties_reset (BaconVideoWidgetProperties *props); +void bacon_video_widget_properties_update (BaconVideoWidgetProperties *props, + BaconVideoWidget *bvw); +void bacon_video_widget_properties_from_metadata (BaconVideoWidgetProperties *props, + const char *title, + const char *artist, + const char *album); +void bacon_video_widget_properties_from_time (BaconVideoWidgetProperties *props, + int time); + +char *bacon_video_widget_properties_time_to_string (int time); + +#endif /* BACON_VIDEO_WIDGET_PROPERTIES_H */ diff --git a/trunk/src/bacon-volume.c b/trunk/src/bacon-volume.c new file mode 100644 index 000000000..9549519d8 --- /dev/null +++ b/trunk/src/bacon-volume.c @@ -0,0 +1,846 @@ +/* Volume Button / popup widget + * (c) copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _GNU_SOURCE +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include "bacon-volume.h" + +#define SCALE_SIZE 100 +#define CLICK_TIMEOUT 250 + +enum { + SIGNAL_VALUE_CHANGED, + NUM_SIGNALS +}; + +struct BaconVolumeButton { + GtkButton parent; + + /* popup */ + GtkWidget *dock, *scale, *image, *plus, *min; + GtkIconSize size; + gint click_id; + float direction; + guint32 pop_time; + GdkPixbuf *icon[4]; + GtkTooltips *tooltips; + guint timeout : 1; +}; + +static void bacon_volume_button_class_init (BaconVolumeButtonClass * klass); +static void bacon_volume_button_init (BaconVolumeButton * button); +static void bacon_volume_button_dispose (GObject * object); + +static gboolean bacon_volume_button_scroll (GtkWidget * widget, + GdkEventScroll * event); +static gboolean bacon_volume_button_press (GtkWidget * widget, + GdkEventButton * event); +static gboolean bacon_volume_key_release (GtkWidget * widget, + GdkEventKey * event); +static void bacon_volume_button_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static gboolean cb_dock_button_press (GtkWidget * widget, + GdkEventButton * event, + gpointer data); +static gboolean cb_dock_key_release (GtkWidget * widget, + GdkEventKey * event, + gpointer data); +static gboolean cb_dock_key_press (GtkWidget * widget, + GdkEventKey * event, + gpointer data); + +static gboolean cb_button_press (GtkWidget * widget, + GdkEventButton * event, + gpointer data); +static gboolean cb_button_release (GtkWidget * widget, + GdkEventButton * event, + gpointer data); +static void bacon_volume_button_update_icon (BaconVolumeButton *button); +static void bacon_volume_scale_value_changed(GtkRange * range); +static void bacon_volume_button_load_icons (GtkWidget * widget); + +/* see below for scale definitions */ +static GtkWidget *bacon_volume_scale_new (BaconVolumeButton * button, + float min, float max, + float step); + +static GtkButtonClass *parent_class = NULL; +static guint signals[NUM_SIGNALS] = { 0 }; + +GType +bacon_volume_button_get_type (void) +{ + static GType bacon_volume_button_type = 0; + + if (G_UNLIKELY (bacon_volume_button_type == 0)) { + const GTypeInfo bacon_volume_button_info = { + sizeof (BaconVolumeButtonClass), + NULL, + NULL, + (GClassInitFunc) bacon_volume_button_class_init, + NULL, + NULL, + sizeof (BaconVolumeButton), + 0, + (GInstanceInitFunc) bacon_volume_button_init, + NULL + }; + + bacon_volume_button_type = + g_type_register_static (GTK_TYPE_BUTTON, + "BaconVolumeButton", + &bacon_volume_button_info, 0); + } + + return bacon_volume_button_type; +} + +static void +bacon_volume_button_class_init (BaconVolumeButtonClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + /* events */ + gobject_class->dispose = bacon_volume_button_dispose; + gtkwidget_class->button_press_event = bacon_volume_button_press; + gtkwidget_class->key_release_event = bacon_volume_key_release; + gtkwidget_class->scroll_event = bacon_volume_button_scroll; + gtkwidget_class->style_set = bacon_volume_button_style_set; + + /* signals */ + signals[SIGNAL_VALUE_CHANGED] = g_signal_new ("value-changed", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BaconVolumeButtonClass, value_changed), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +static void +bacon_volume_button_init (BaconVolumeButton *button) +{ + button->timeout = FALSE; + button->click_id = 0; + button->dock = button->scale = NULL; + button->tooltips = gtk_tooltips_new (); +} + +static void +bacon_volume_button_dispose (GObject *object) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (object); + guint i; + + if (button->dock) { + gtk_widget_destroy (button->dock); + button->dock = NULL; + } + + if (button->click_id != 0) { + g_source_remove (button->click_id); + button->click_id = 0; + } + for (i = 0; i < 4; i++) { + if (button->icon[i] != NULL) { + g_object_unref (button->icon[i]); + button->icon[i] = NULL; + } + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/* + * public API. + */ + +GtkWidget * +bacon_volume_button_new (GtkIconSize size, + float min, float max, + float step) +{ + BaconVolumeButton *button; + GtkWidget *frame, *box; + + button = g_object_new (BACON_TYPE_VOLUME_BUTTON, NULL); + atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (button)), + _("Volume")); + button->size = size; + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE); + + /* image */ + button->image = gtk_image_new (); + gtk_container_add (GTK_CONTAINER (button), button->image); + gtk_widget_show_all (button->image); + + /* window */ + button->dock = gtk_window_new (GTK_WINDOW_POPUP); + g_signal_connect (button->dock, "button-press-event", + G_CALLBACK (cb_dock_button_press), button); + g_signal_connect (button->dock, "key-release-event", + G_CALLBACK (cb_dock_key_release), button); + g_signal_connect (button->dock, "key-press-event", + G_CALLBACK (cb_dock_key_press), button); + gtk_window_set_decorated (GTK_WINDOW (button->dock), FALSE); + + /* frame */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (button->dock), frame); + box = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (frame), box); + + /* + */ + button->plus = gtk_button_new_with_label (_("+")); + atk_object_set_name (gtk_widget_get_accessible (button->plus), + _("Volume Down")); + gtk_button_set_relief (GTK_BUTTON (button->plus), GTK_RELIEF_NONE); + g_signal_connect (button->plus, "button-press-event", + G_CALLBACK (cb_button_press), button); + g_signal_connect (button->plus, "button-release-event", + G_CALLBACK (cb_button_release), button); + gtk_box_pack_start (GTK_BOX (box), button->plus, TRUE, FALSE, 0); + + /* scale */ + button->scale = bacon_volume_scale_new (button, min, max, step); + gtk_widget_set_size_request (button->scale, -1, SCALE_SIZE); + gtk_scale_set_draw_value (GTK_SCALE (button->scale), FALSE); + gtk_range_set_inverted (GTK_RANGE (button->scale), TRUE); + gtk_box_pack_start (GTK_BOX (box), button->scale, TRUE, FALSE, 0); + + /* - */ + button->min = gtk_button_new_with_label (_("-")); + atk_object_set_name (gtk_widget_get_accessible (button->min), + _("Volume Up")); + gtk_button_set_relief (GTK_BUTTON (button->min), GTK_RELIEF_NONE); + g_signal_connect (button->min, "button-press-event", + G_CALLBACK (cb_button_press), button); + g_signal_connect (button->min, "button-release-event", + G_CALLBACK (cb_button_release), button); + gtk_box_pack_start (GTK_BOX (box), button->min, TRUE, FALSE, 0); + + /* set button text */ + bacon_volume_button_update_icon (button); + + return GTK_WIDGET (button); +} + +float +bacon_volume_button_get_value (BaconVolumeButton * button) +{ + g_return_val_if_fail (button != NULL, 0); + + return gtk_range_get_value (GTK_RANGE (button->scale)); +} + +void +bacon_volume_button_set_value (BaconVolumeButton * button, + float value) +{ + g_return_if_fail (button != NULL); + + gtk_range_set_value (GTK_RANGE (button->scale), value); +} + +/* + * button callbacks. + */ + +static void +bacon_volume_button_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style); + + bacon_volume_button_load_icons (widget); +} + +static gboolean +bacon_volume_button_scroll (GtkWidget * widget, + GdkEventScroll * event) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget); + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale)); + float d; + + if (event->type != GDK_SCROLL) + return FALSE; + + d = bacon_volume_button_get_value (button); + if (event->direction == GDK_SCROLL_UP) { + d += adj->step_increment; + if (d > adj->upper) + d = adj->upper; + } else { + d -= adj->step_increment; + if (d < adj->lower) + d = adj->lower; + } + bacon_volume_button_set_value (button, d); + + return TRUE; +} + +static gboolean +bacon_volume_button_press (GtkWidget * widget, + GdkEventButton * event) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget); + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale)); + gint x, y, m, dx, dy, sx, sy, ystartoff, mouse_y; + float v; + GdkEventButton *e; + GdkDisplay *display; + GdkScreen *screen; + + display = gtk_widget_get_display (widget); + screen = gtk_widget_get_screen (widget); + + /* position roughly */ + gtk_window_set_screen (GTK_WINDOW (button->dock), screen); + + gdk_window_get_origin (widget->window, &x, &y); + x += widget->allocation.x; + y += widget->allocation.y; + gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2)); + gtk_widget_show_all (button->dock); + gdk_window_get_origin (button->dock->window, &dx, &dy); + dy += button->dock->allocation.y; + gdk_window_get_origin (button->scale->window, &sx, &sy); + sy += button->scale->allocation.y; + ystartoff = sy - dy; + mouse_y = event->y; + button->timeout = TRUE; + + /* position (needs widget to be shown already) */ + v = bacon_volume_button_get_value (button) / (adj->upper - adj->lower); + x += (widget->allocation.width - button->dock->allocation.width) / 2; + y -= ystartoff; + y -= GTK_RANGE (button->scale)->min_slider_size / 2; + m = button->scale->allocation.height - + GTK_RANGE (button->scale)->min_slider_size; + y -= m * (1.0 - v); + y += mouse_y; + gtk_window_move (GTK_WINDOW (button->dock), x, y); + gdk_window_get_origin (button->scale->window, &sx, &sy); + + GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); + + /* grab focus */ + gtk_grab_add (button->dock); + + if (gdk_pointer_grab (button->dock->window, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, NULL, NULL, event->time) + != GDK_GRAB_SUCCESS) { + gtk_grab_remove (button->dock); + gtk_widget_hide (button->dock); + return FALSE; + } + + if (gdk_keyboard_grab (button->dock->window, TRUE, event->time) != GDK_GRAB_SUCCESS) { + gdk_display_pointer_ungrab (display, event->time); + gtk_grab_remove (button->dock); + gtk_widget_hide (button->dock); + return FALSE; + } + + gtk_widget_grab_focus (button->dock); + + /* forward event to the slider */ + e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event); + e->window = button->scale->window; + + /* position: the X position isn't relevant, halfway will work just fine. + * The vertical position should be *exactly* in the middle of the slider + * of the scale; if we don't do that correctly, it'll move from its current + * position, which means a position change on-click, which is bad. */ + e->x = button->scale->allocation.width / 2; + m = button->scale->allocation.height - + GTK_RANGE (button->scale)->min_slider_size; + e->y = ((1.0 - v) * m) + GTK_RANGE (button->scale)->min_slider_size / 2; + gtk_widget_event (button->scale, (GdkEvent *) e); + e->window = event->window; + gdk_event_free ((GdkEvent *) e); + + button->pop_time = event->time; + + return TRUE; +} + +static gboolean +bacon_volume_key_release (GtkWidget * widget, + GdkEventKey * event) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget); + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale)); + gint x, y, m, dx, dy, sx, sy, ystartoff; + float v; + GdkDisplay *display; + GdkScreen *screen; + + if (event->keyval != GDK_space && event->keyval != GDK_Return) + return FALSE; + + display = gtk_widget_get_display (widget); + screen = gtk_widget_get_screen (widget); + + /* position roughly */ + gtk_window_set_screen (GTK_WINDOW (button->dock), screen); + + gdk_window_get_origin (widget->window, &x, &y); + x += widget->allocation.x; + y += widget->allocation.y; + gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2)); + gtk_widget_show_all (button->dock); + gdk_window_get_origin (button->dock->window, &dx, &dy); + dy += button->dock->allocation.y; + gdk_window_get_origin (button->scale->window, &sx, &sy); + sy += button->scale->allocation.y; + ystartoff = sy - dy; + button->timeout = TRUE; + + /* position (needs widget to be shown already) */ + v = bacon_volume_button_get_value (button) / (adj->upper - adj->lower); + x += (widget->allocation.width - button->dock->allocation.width) / 2; + y -= ystartoff; + y -= GTK_RANGE (button->scale)->min_slider_size / 2; + m = button->scale->allocation.height - + GTK_RANGE (button->scale)->min_slider_size; + y -= m * (1.0 - v); + gtk_window_move (GTK_WINDOW (button->dock), x, y); + gdk_window_get_origin (button->scale->window, &sx, &sy); + + /* grab focus */ + gtk_grab_add (button->dock); + + if (gdk_pointer_grab (button->dock->window, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, + NULL, NULL, event->time) + != GDK_GRAB_SUCCESS) { + gtk_grab_remove (button->dock); + gtk_widget_hide (button->dock); + return FALSE; + } + + if (gdk_keyboard_grab (button->dock->window, TRUE, event->time) != GDK_GRAB_SUCCESS) { + gdk_display_pointer_ungrab (display, event->time); + gtk_grab_remove (button->dock); + gtk_widget_hide (button->dock); + return FALSE; + } + + gtk_widget_grab_focus (button->scale); + + button->pop_time = event->time; + + return TRUE; +} + +/* + * +/- button callbacks. + */ + +static gboolean +cb_button_timeout (gpointer data) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (data); + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale)); + float val; + gboolean res = TRUE; + + if (button->click_id == 0) + return FALSE; + + val = bacon_volume_button_get_value (button); + val += button->direction; + if (val <= adj->lower) { + res = FALSE; + val = adj->lower; + } else if (val > adj->upper) { + res = FALSE; + val = adj->upper; + } + bacon_volume_button_set_value (button, val); + + if (!res) { + g_source_remove (button->click_id); + button->click_id = 0; + } + + return res; +} + +static gboolean +cb_button_press (GtkWidget * widget, + GdkEventButton * event, + gpointer data) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (data); + GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale)); + + if (button->click_id != 0) + g_source_remove (button->click_id); + button->direction = (widget == button->plus) ? + fabs (adj->page_increment) : - fabs (adj->page_increment); + button->click_id = g_timeout_add (CLICK_TIMEOUT, + (GSourceFunc) cb_button_timeout, button); + cb_button_timeout (button); + + return TRUE; +} + +static gboolean +cb_button_release (GtkWidget * widget, + GdkEventButton * event, + gpointer data) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (data); + + if (button->click_id != 0) { + g_source_remove (button->click_id); + button->click_id = 0; + } + + return TRUE; +} + +/* + * Scale callbacks. + */ + +static void +bacon_volume_release_grab (BaconVolumeButton *button, + GdkEventButton * event) +{ + GdkEventButton *e; + GdkDisplay *display; + + /* ungrab focus */ + display = gtk_widget_get_display (GTK_WIDGET (button)); + gdk_display_keyboard_ungrab (display, event->time); + gdk_display_pointer_ungrab (display, event->time); + gtk_grab_remove (button->dock); + + /* hide again */ + gtk_widget_hide (button->dock); + button->timeout = FALSE; + + e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event); + e->window = GTK_WIDGET (button)->window; + e->type = GDK_BUTTON_RELEASE; + gtk_widget_event (GTK_WIDGET (button), (GdkEvent *) e); + e->window = event->window; + gdk_event_free ((GdkEvent *) e); +} + +static gboolean +cb_dock_button_press (GtkWidget * widget, + GdkEventButton * event, + gpointer data) +{ + //GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *) event); + BaconVolumeButton *button = BACON_VOLUME_BUTTON (data); + + if (/*ewidget == button->dock &&*/ event->type == GDK_BUTTON_PRESS) { + bacon_volume_release_grab (button, event); + return TRUE; + } + + return FALSE; +} + +static gboolean +cb_dock_key_release (GtkWidget * widget, + GdkEventKey * event, + gpointer data) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (data); + + if (event->keyval == GDK_Escape) { + GdkDisplay *display; + + /* ungrab focus */ + display = gtk_widget_get_display (widget); + gdk_display_keyboard_ungrab (display, event->time); + gdk_display_pointer_ungrab (display, event->time); + gtk_grab_remove (button->dock); + + /* hide again */ + gtk_widget_hide (button->dock); + button->timeout = FALSE; + + return TRUE; + } + return FALSE; +} + +static gboolean +cb_dock_key_press (GtkWidget * widget, + GdkEventKey * event, + gpointer data) +{ + if (event->keyval == GDK_Escape) { + return TRUE; + } + return FALSE; +} + +/* + * Scale stuff. + */ + +#define BACON_TYPE_VOLUME_SCALE \ + (bacon_volume_scale_get_type ()) +#define BACON_VOLUME_SCALE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_VOLUME_SCALE, \ + BaconVolumeScale)) + +typedef struct _BaconVolumeScale { + GtkVScale parent; + BaconVolumeButton *button; +} BaconVolumeScale; + +static GType bacon_volume_scale_get_type (void); + +static void bacon_volume_scale_class_init (GtkVScaleClass * klass); + +static gboolean bacon_volume_scale_press (GtkWidget * widget, + GdkEventButton * event); +static gboolean bacon_volume_scale_release (GtkWidget * widget, + GdkEventButton * event); + +static GtkVScaleClass *scale_parent_class = NULL; + +static GType +bacon_volume_scale_get_type (void) +{ + static GType bacon_volume_scale_type = 0; + + if (G_UNLIKELY (bacon_volume_scale_type == 0)) { + const GTypeInfo bacon_volume_scale_info = { + sizeof (GtkVScaleClass), + NULL, + NULL, + (GClassInitFunc) bacon_volume_scale_class_init, + NULL, + NULL, + sizeof (BaconVolumeScale), + 0, + NULL, + NULL + }; + + bacon_volume_scale_type = + g_type_register_static (GTK_TYPE_VSCALE, + "BaconVolumeScale", + &bacon_volume_scale_info, 0); + } + + return bacon_volume_scale_type; +} + +static void +bacon_volume_scale_class_init (GtkVScaleClass * klass) +{ + GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass); + GtkRangeClass *gtkrange_class = GTK_RANGE_CLASS (klass); + + scale_parent_class = g_type_class_peek_parent (klass); + + gtkwidget_class->button_press_event = bacon_volume_scale_press; + gtkwidget_class->button_release_event = bacon_volume_scale_release; + gtkrange_class->value_changed = bacon_volume_scale_value_changed; +} + +static GtkWidget * +bacon_volume_scale_new (BaconVolumeButton * button, + float min, float max, + float step) +{ + BaconVolumeScale *scale = g_object_new (BACON_TYPE_VOLUME_SCALE, NULL); + GtkObject *adj; + + adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0); + gtk_range_set_adjustment (GTK_RANGE (scale), GTK_ADJUSTMENT (adj)); + scale->button = button; + + return GTK_WIDGET (scale); +} + +static gboolean +bacon_volume_scale_press (GtkWidget * widget, + GdkEventButton * event) +{ + BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget); + BaconVolumeButton *button = scale->button; + + /* the scale will grab input; if we have input grabbed, all goes + * horribly wrong, so let's not do that. */ + gtk_grab_remove (button->dock); + + return GTK_WIDGET_CLASS (scale_parent_class)->button_press_event (widget, event); +} + +static gboolean +bacon_volume_scale_release (GtkWidget * widget, + GdkEventButton * event) +{ + BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget); + BaconVolumeButton *button = scale->button; + gboolean res; + + if (button->timeout) { + /* if we did a quick click, leave the window open; else, hide it */ + if (event->time > button->pop_time + CLICK_TIMEOUT) { + bacon_volume_release_grab (button, event); + GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event); + return TRUE; + } + button->timeout = FALSE; + } + + res = GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event); + + /* the scale will release input; right after that, we *have to* grab + * it back so we can catch out-of-scale clicks and hide the popup, + * so I basically want a g_signal_connect_after_always(), but I can't + * find that, so we do this complex 'first-call-parent-then-do-actual- + * action' thingy... */ + gtk_grab_add (button->dock); + + return res; +} + +static void +bacon_volume_button_update_icon (BaconVolumeButton *button) +{ + GtkRange *range = GTK_RANGE (button->scale); + float val = gtk_range_get_value (range); + GtkAdjustment *adj; + GdkPixbuf *pixbuf; + float step; + + adj = gtk_range_get_adjustment (range); + step = (adj->upper - adj->lower) / 4; + + if (val == adj->lower) + pixbuf = button->icon[0]; + else if (val > adj->lower && val <= adj->lower + step) + pixbuf = button->icon[1]; + else if (val > adj->lower + step && val <= adj->lower + step * 2) + pixbuf = button->icon[2]; + else + pixbuf = button->icon[3]; + + gtk_image_set_from_pixbuf (GTK_IMAGE (button->image), pixbuf); +} + +static void +bacon_volume_button_update_tip (BaconVolumeButton *button) +{ + GtkRange *range = GTK_RANGE (button->scale); + float val = gtk_range_get_value (range); + GtkAdjustment *adj; + char *str; + + adj = gtk_range_get_adjustment (range); + + if (val == adj->lower) { + str = g_strdup (_("Muted")); + } else if (val == adj->upper) { + str = g_strdup (_("Full Volume")); + } else { + str = g_strdup_printf ("%d %%", + (int) ((val - adj->lower) / (adj->upper - adj->lower) * 100)); + } + + gtk_tooltips_set_tip (button->tooltips, GTK_WIDGET (button), + str, NULL); + g_free (str); +} + +static void +bacon_volume_scale_value_changed (GtkRange * range) +{ + BaconVolumeScale *scale = BACON_VOLUME_SCALE (range); + BaconVolumeButton *button = scale->button; + + bacon_volume_button_update_icon (button); + bacon_volume_button_update_tip (button); + + /* signal */ + g_signal_emit (button, signals[SIGNAL_VALUE_CHANGED], 0); +} + +static void +bacon_volume_button_load_icons (GtkWidget *widget) +{ + BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget); + guint i; + gint w, h; + GdkScreen *screen; + GtkIconTheme *theme; + const char *icon_name[] = {"audio-volume-muted", "audio-volume-low", + "audio-volume-medium", "audio-volume-high"}; + const char *fallback_icon_name[] = {"stock_volume-0", "stock_volume-min", + "stock_volume-med", "stock_volume-max"}; + + screen = gtk_widget_get_screen (widget); + theme = gtk_icon_theme_get_for_screen (screen); + + gtk_icon_size_lookup (button->size, &w, &h); + + for (i = 0; i < 4; i++) { + GError *error = NULL; + if (button->icon[i] != NULL) { + g_object_unref (button->icon[i]); + button->icon[i] = NULL; + } + button->icon[i] = gtk_icon_theme_load_icon (theme, icon_name[i], w, 0, &error); + if (error) { + g_print ("Couldn't load themed icon '%s': %s\n", icon_name[i], error->message); + g_clear_error (&error); + + button->icon[i] = gtk_icon_theme_load_icon (theme, fallback_icon_name[i], w, 0, &error); + if (error) { + g_print ("Couldn't load themed icon '%s': %s\n", icon_name[i], error->message); + g_clear_error (&error); + } + } + } + + /* Apply the new icons */ + bacon_volume_button_update_icon (button); +} + +/* + * vim: sw=2 ts=8 cindent noai bs=2 + */ diff --git a/trunk/src/bacon-volume.h b/trunk/src/bacon-volume.h new file mode 100644 index 000000000..3c7205cad --- /dev/null +++ b/trunk/src/bacon-volume.h @@ -0,0 +1,57 @@ +/* Volume Button / popup widget + * (c) copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __BACON_VOLUME_BUTTON_H__ +#define __BACON_VOLUME_BUTTON_H__ + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkicontheme.h> + +G_BEGIN_DECLS + +#define BACON_TYPE_VOLUME_BUTTON \ + (bacon_volume_button_get_type ()) +#define BACON_VOLUME_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_VOLUME_BUTTON, \ + BaconVolumeButton)) + +typedef struct BaconVolumeButton BaconVolumeButton; + +typedef struct _BaconVolumeButtonClass { + GtkButtonClass parent_class; + + /* signals */ + void (* value_changed) (BaconVolumeButton * button); + + gpointer __bla[4]; +} BaconVolumeButtonClass; + +GType bacon_volume_button_get_type (void); + +GtkWidget * bacon_volume_button_new (GtkIconSize size, + float min, float max, + float step); +float bacon_volume_button_get_value (BaconVolumeButton * button); +void bacon_volume_button_set_value (BaconVolumeButton * button, + float value); + +G_END_DECLS + +#endif /* __BACON_VOLUME_BUTTON_H__ */ diff --git a/trunk/src/baconvideowidget-marshal.list b/trunk/src/baconvideowidget-marshal.list new file mode 100644 index 000000000..09bedf128 --- /dev/null +++ b/trunk/src/baconvideowidget-marshal.list @@ -0,0 +1,2 @@ +VOID:INT64,INT64,FLOAT,BOOLEAN +VOID:STRING,BOOLEAN,BOOLEAN diff --git a/trunk/src/disc-test.c b/trunk/src/disc-test.c new file mode 100644 index 000000000..1c4c9cd31 --- /dev/null +++ b/trunk/src/disc-test.c @@ -0,0 +1,111 @@ +/* Small test app for disc concent detection + * (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib.h> +#include <libgnomevfs/gnome-vfs.h> + +#include "totem-disc.h" + +gint +main (gint argc, + gchar *argv[]) +{ + MediaType type; + GError *error = NULL; + const char *type_s = NULL; + char *url = NULL; + gboolean is_dir = FALSE; + GList *or, *list; + GnomeVFSVolumeMonitor *mon; + + if (argc != 2) { + g_print ("Usage: %s <device>\n", argv[0]); + return -1; + } + + g_type_init (); + gnome_vfs_init (); + g_log_set_always_fatal (G_LOG_LEVEL_WARNING); + + if (g_file_test (argv[1], G_FILE_TEST_IS_DIR) != FALSE) { + type = totem_cd_detect_type_from_dir (argv[1], &url, &error); + is_dir = TRUE; + } else { + type = totem_cd_detect_type (argv[1], &error); + } + + switch (type) { + case MEDIA_TYPE_ERROR: + mon = gnome_vfs_get_volume_monitor (); + g_print ("Error: %s\n", error ? error->message : "unknown reason"); + g_print ("\n"); + g_print ("List of connected drives:\n"); + for (or = list = gnome_vfs_volume_monitor_get_connected_drives (mon); + list != NULL; list = list->next) { + char *device; + device = gnome_vfs_drive_get_device_path ((GnomeVFSDrive *) list->data); + g_print ("%s\n", device); + g_free (device); + } + if (or == NULL) + g_print ("No connected drives!\n"); + + g_print ("List of mounted volumes:\n"); + for (or = list = gnome_vfs_volume_monitor_get_mounted_volumes (mon); + list != NULL; list = list->next) { + char *device; + + device = gnome_vfs_volume_get_device_path ((GnomeVFSVolume *) list->data); + g_print ("%s\n", device); + g_free (device); + } + if (or == NULL) + g_print ("No mounted volumes!\n"); + + return -1; + case MEDIA_TYPE_DATA: + type_s = "Data CD"; + break; + case MEDIA_TYPE_CDDA: + type_s = "Audio CD"; + break; + case MEDIA_TYPE_VCD: + type_s = "Video CD"; + break; + case MEDIA_TYPE_DVD: + type_s = "DVD"; + break; + default: + g_assert_not_reached (); + } + + g_print ("%s contains a %s\n", argv[1], type_s); + + if (is_dir != FALSE && url != NULL) { + g_print ("URL for directory is %s\n", url); + } + + g_free (url); + + return 0; +} diff --git a/trunk/src/ev-sidebar.c b/trunk/src/ev-sidebar.c new file mode 100644 index 000000000..0f8d01eb3 --- /dev/null +++ b/trunk/src/ev-sidebar.c @@ -0,0 +1,451 @@ +/* this file is part of evince, a gnome document viewer + * + * Copyright (C) 2004 Red Hat, Inc. + * + * Author: + * Jonathan Blandford <jrb@alum.mit.edu> + * + * Evince 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. + * + * Evince 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "ev-sidebar.h" + +typedef struct +{ + char *id; + char *title; + GtkWidget *main_widget; +} EvSidebarPage; + +enum +{ + PAGE_COLUMN_ID, + PAGE_COLUMN_TITLE, + PAGE_COLUMN_MENU_ITEM, + PAGE_COLUMN_MAIN_WIDGET, + PAGE_COLUMN_NOTEBOOK_INDEX, + PAGE_COLUMN_NUM_COLS +}; + +struct _EvSidebarPrivate { + GtkWidget *notebook; + GtkWidget *menu; + GtkWidget *hbox; + GtkWidget *label; + + GtkTreeModel *page_model; + char *current; +}; + +enum { + CLOSED, + LAST_SIGNAL +}; + +static int ev_sidebar_table_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (EvSidebar, ev_sidebar, GTK_TYPE_VBOX) + +#define EV_SIDEBAR_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EV_TYPE_SIDEBAR, EvSidebarPrivate)) + +static void +ev_sidebar_destroy (GtkObject *object) +{ + EvSidebar *ev_sidebar = EV_SIDEBAR (object); + + g_free (ev_sidebar->priv->current); + ev_sidebar->priv->current = NULL; + + if (ev_sidebar->priv->menu) { + gtk_menu_detach (GTK_MENU (ev_sidebar->priv->menu)); + ev_sidebar->priv->menu = NULL; + } + + (* GTK_OBJECT_CLASS (ev_sidebar_parent_class)->destroy) (object); +} + +static void +ev_sidebar_class_init (EvSidebarClass *ev_sidebar_class) +{ + GObjectClass *g_object_class; + GtkWidgetClass *widget_class; + GtkObjectClass *gtk_object_klass; + + g_object_class = G_OBJECT_CLASS (ev_sidebar_class); + widget_class = GTK_WIDGET_CLASS (ev_sidebar_class); + gtk_object_klass = GTK_OBJECT_CLASS (ev_sidebar_class); + + g_type_class_add_private (g_object_class, sizeof (EvSidebarPrivate)); + + gtk_object_klass->destroy = ev_sidebar_destroy; + + ev_sidebar_table_signals[CLOSED] = + g_signal_new ("closed", + G_TYPE_FROM_CLASS (g_object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EvSidebarClass, closed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + +} + +static void +ev_sidebar_menu_position_under (GtkMenu *menu, + int *x, + int *y, + gboolean *push_in, + gpointer user_data) +{ + GtkWidget *widget; + + g_return_if_fail (GTK_IS_BUTTON (user_data)); + g_return_if_fail (GTK_WIDGET_NO_WINDOW (user_data)); + + widget = GTK_WIDGET (user_data); + + gdk_window_get_origin (widget->window, x, y); + + *x += widget->allocation.x; + *y += widget->allocation.y + widget->allocation.height; + + *push_in = FALSE; +} + +static gboolean +ev_sidebar_select_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + EvSidebar *ev_sidebar = EV_SIDEBAR (user_data); + + if (event->button == 1) { + GtkRequisition requisition; + gint width; + + width = widget->allocation.width; + gtk_widget_set_size_request (ev_sidebar->priv->menu, -1, -1); + gtk_widget_size_request (ev_sidebar->priv->menu, &requisition); + gtk_widget_set_size_request (ev_sidebar->priv->menu, + MAX (width, requisition.width), -1); + gtk_widget_grab_focus (widget); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + gtk_menu_popup (GTK_MENU (ev_sidebar->priv->menu), + NULL, NULL, ev_sidebar_menu_position_under, widget, + event->button, event->time); + + return TRUE; + } + + return FALSE; +} + +static gboolean +ev_sidebar_select_button_key_press_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + EvSidebar *ev_sidebar = EV_SIDEBAR (user_data); + + if (event->keyval == GDK_space || + event->keyval == GDK_KP_Space || + event->keyval == GDK_Return || + event->keyval == GDK_KP_Enter) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + gtk_menu_popup (GTK_MENU (ev_sidebar->priv->menu), + NULL, NULL, ev_sidebar_menu_position_under, widget, + 1, event->time); + return TRUE; + } + + return FALSE; +} + +static void +ev_sidebar_close_clicked_cb (GtkWidget *widget, + gpointer user_data) +{ + EvSidebar *ev_sidebar = EV_SIDEBAR (user_data); + + g_signal_emit (G_OBJECT (ev_sidebar), + ev_sidebar_table_signals[CLOSED], 0, NULL); + gtk_widget_hide (GTK_WIDGET (ev_sidebar)); +} + +static void +ev_sidebar_menu_deactivate_cb (GtkWidget *widget, + gpointer user_data) +{ + GtkWidget *menu_button; + + menu_button = GTK_WIDGET (user_data); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button), FALSE); +} + +static void +ev_sidebar_menu_detach_cb (GtkWidget *widget, + GtkMenu *menu) +{ + EvSidebar *ev_sidebar = EV_SIDEBAR (widget); + + ev_sidebar->priv->menu = NULL; +} + +static void +ev_sidebar_menu_item_activate_cb (GtkWidget *widget, + gpointer user_data) +{ + EvSidebar *ev_sidebar = EV_SIDEBAR (user_data); + GtkTreeIter iter; + GtkWidget *menu_item, *item; + gchar *title, *page_id; + gboolean valid; + gint index; + + menu_item = gtk_menu_get_active (GTK_MENU (ev_sidebar->priv->menu)); + valid = gtk_tree_model_get_iter_first (ev_sidebar->priv->page_model, &iter); + + while (valid) { + gtk_tree_model_get (ev_sidebar->priv->page_model, + &iter, + PAGE_COLUMN_ID, &page_id, + PAGE_COLUMN_TITLE, &title, + PAGE_COLUMN_MENU_ITEM, &item, + PAGE_COLUMN_NOTEBOOK_INDEX, &index, + -1); + + if (item == menu_item) { + gtk_notebook_set_current_page + (GTK_NOTEBOOK (ev_sidebar->priv->notebook), index); + gtk_label_set_text (GTK_LABEL (ev_sidebar->priv->label), title); + g_free (ev_sidebar->priv->current); + ev_sidebar->priv->current = page_id; + valid = FALSE; + } else { + valid = gtk_tree_model_iter_next (ev_sidebar->priv->page_model, &iter); + g_free (page_id); + } + g_object_unref (item); + g_free (title); + } +} + +static void +ev_sidebar_init (EvSidebar *ev_sidebar) +{ + GtkWidget *hbox; + GtkWidget *close_button; + GtkWidget *select_button; + GtkWidget *select_hbox; + GtkWidget *arrow; + GtkWidget *image; + + ev_sidebar->priv = EV_SIDEBAR_GET_PRIVATE (ev_sidebar); + + /* data model */ + ev_sidebar->priv->page_model = (GtkTreeModel *) + gtk_list_store_new (PAGE_COLUMN_NUM_COLS, + G_TYPE_STRING, + G_TYPE_STRING, + GTK_TYPE_WIDGET, + GTK_TYPE_WIDGET, + G_TYPE_INT); + + /* top option menu */ + hbox = gtk_hbox_new (FALSE, 0); + ev_sidebar->priv->hbox = hbox; + gtk_box_pack_start (GTK_BOX (ev_sidebar), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + select_button = gtk_toggle_button_new (); + gtk_button_set_relief (GTK_BUTTON (select_button), GTK_RELIEF_NONE); + g_signal_connect (select_button, "button_press_event", + G_CALLBACK (ev_sidebar_select_button_press_cb), + ev_sidebar); + g_signal_connect (select_button, "key_press_event", + G_CALLBACK (ev_sidebar_select_button_key_press_cb), + ev_sidebar); + + select_hbox = gtk_hbox_new (FALSE, 0); + + ev_sidebar->priv->label = gtk_label_new (""); + gtk_box_pack_start (GTK_BOX (select_hbox), + ev_sidebar->priv->label, + FALSE, FALSE, 0); + gtk_widget_show (ev_sidebar->priv->label); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_box_pack_end (GTK_BOX (select_hbox), arrow, FALSE, FALSE, 0); + gtk_widget_show (arrow); + + gtk_container_add (GTK_CONTAINER (select_button), select_hbox); + gtk_widget_show (select_hbox); + + gtk_box_pack_start (GTK_BOX (hbox), select_button, TRUE, TRUE, 0); + gtk_widget_show (select_button); + + close_button = gtk_button_new (); + gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE); + g_signal_connect (close_button, "clicked", + G_CALLBACK (ev_sidebar_close_clicked_cb), + ev_sidebar); + + image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, + GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (close_button), image); + gtk_widget_show (image); + + gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0); + gtk_widget_show (close_button); + + ev_sidebar->priv->menu = gtk_menu_new (); + g_signal_connect (ev_sidebar->priv->menu, "deactivate", + G_CALLBACK (ev_sidebar_menu_deactivate_cb), + select_button); + gtk_menu_attach_to_widget (GTK_MENU (ev_sidebar->priv->menu), + GTK_WIDGET (ev_sidebar), + ev_sidebar_menu_detach_cb); + gtk_widget_show (ev_sidebar->priv->menu); + + ev_sidebar->priv->notebook = gtk_notebook_new (); + gtk_notebook_set_show_border (GTK_NOTEBOOK (ev_sidebar->priv->notebook), FALSE); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (ev_sidebar->priv->notebook), FALSE); + gtk_box_pack_start (GTK_BOX (ev_sidebar), ev_sidebar->priv->notebook, + TRUE, TRUE, 0); + gtk_widget_show (ev_sidebar->priv->notebook); +} + +/* Public functions */ + +GtkWidget * +ev_sidebar_new (void) +{ + GtkWidget *ev_sidebar; + + ev_sidebar = g_object_new (EV_TYPE_SIDEBAR, NULL); + + return ev_sidebar; +} + +const char * +ev_sidebar_get_current_page (EvSidebar *ev_sidebar) +{ + g_return_val_if_fail (EV_IS_SIDEBAR (ev_sidebar), NULL); + g_return_val_if_fail (ev_sidebar->priv != NULL, NULL); + + return ev_sidebar->priv->current; +} + +void +ev_sidebar_set_current_page (EvSidebar *ev_sidebar, const char *new_page_id) +{ + GtkTreeIter iter; + GtkWidget *menu_item; + gchar *page_id; + gboolean valid; + gint index; + + g_return_if_fail (EV_IS_SIDEBAR (ev_sidebar)); + g_return_if_fail (new_page_id != NULL); + + if (strcmp (new_page_id, ev_sidebar->priv->current) == 0) + return; + + valid = gtk_tree_model_get_iter_first (ev_sidebar->priv->page_model, &iter); + + while (valid) { + gtk_tree_model_get (ev_sidebar->priv->page_model, + &iter, + PAGE_COLUMN_ID, &page_id, + PAGE_COLUMN_MENU_ITEM, &menu_item, + PAGE_COLUMN_NOTEBOOK_INDEX, &index, + -1); + + if (page_id != NULL && strcmp (new_page_id, page_id) == 0) { + gtk_menu_set_active (GTK_MENU (ev_sidebar->priv->menu), index); + gtk_menu_item_activate (GTK_MENU_ITEM (menu_item)); + valid = FALSE; + } else { + valid = gtk_tree_model_iter_next (ev_sidebar->priv->page_model, &iter); + } + g_object_unref (menu_item); + g_free (page_id); + } +} + +void +ev_sidebar_add_page (EvSidebar *ev_sidebar, + const gchar *page_id, + const gchar *title, + GtkWidget *main_widget) +{ + GtkTreeIter iter; + GtkWidget *menu_item; + gchar *label_title, *new_page_id; + int index; + + g_return_if_fail (EV_IS_SIDEBAR (ev_sidebar)); + g_return_if_fail (page_id != NULL); + g_return_if_fail (title != NULL); + g_return_if_fail (GTK_IS_WIDGET (main_widget)); + + index = gtk_notebook_append_page (GTK_NOTEBOOK (ev_sidebar->priv->notebook), + main_widget, NULL); + + menu_item = gtk_image_menu_item_new_with_label (title); + g_signal_connect (menu_item, "activate", + G_CALLBACK (ev_sidebar_menu_item_activate_cb), + ev_sidebar); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (ev_sidebar->priv->menu), + menu_item); + + gtk_list_store_insert_with_values (GTK_LIST_STORE (ev_sidebar->priv->page_model), + &iter, 0, + PAGE_COLUMN_ID, page_id, + PAGE_COLUMN_TITLE, title, + PAGE_COLUMN_MENU_ITEM, menu_item, + PAGE_COLUMN_MAIN_WIDGET, main_widget, + PAGE_COLUMN_NOTEBOOK_INDEX, index, + -1); + + /* Set the first item added as active */ + gtk_tree_model_get_iter_first (ev_sidebar->priv->page_model, &iter); + gtk_tree_model_get (ev_sidebar->priv->page_model, + &iter, + PAGE_COLUMN_ID, &new_page_id, + PAGE_COLUMN_TITLE, &label_title, + PAGE_COLUMN_NOTEBOOK_INDEX, &index, + -1); + + gtk_menu_set_active (GTK_MENU (ev_sidebar->priv->menu), index); + gtk_label_set_text (GTK_LABEL (ev_sidebar->priv->label), label_title); + gtk_notebook_set_current_page (GTK_NOTEBOOK (ev_sidebar->priv->notebook), + index); + ev_sidebar->priv->current = new_page_id; + g_free (label_title); +} + diff --git a/trunk/src/ev-sidebar.h b/trunk/src/ev-sidebar.h new file mode 100644 index 000000000..9e6a42e3e --- /dev/null +++ b/trunk/src/ev-sidebar.h @@ -0,0 +1,71 @@ +/* ev-sidebar.h + * this file is part of evince, a gnome document viewer + * + * Copyright (C) 2004 Red Hat, Inc. + * + * Author: + * Jonathan Blandford <jrb@alum.mit.edu> + * + * Evince 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. + * + * Evince 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __EV_SIDEBAR_H__ +#define __EV_SIDEBAR_H__ + +#include <gtk/gtkvbox.h> + +G_BEGIN_DECLS + +typedef struct _EvSidebar EvSidebar; +typedef struct _EvSidebarClass EvSidebarClass; +typedef struct _EvSidebarPrivate EvSidebarPrivate; + +#define EV_TYPE_SIDEBAR (ev_sidebar_get_type()) +#define EV_SIDEBAR(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EV_TYPE_SIDEBAR, EvSidebar)) +#define EV_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EV_TYPE_SIDEBAR, EvSidebarClass)) +#define EV_IS_SIDEBAR(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EV_TYPE_SIDEBAR)) +#define EV_IS_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EV_TYPE_SIDEBAR)) +#define EV_SIDEBAR_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), EV_TYPE_SIDEBAR, EvSidebarClass)) + +struct _EvSidebar { + GtkVBox base_instance; + + EvSidebarPrivate *priv; +}; + +struct _EvSidebarClass { + GtkVBoxClass base_class; + + void (*closed) (EvSidebar *sidebar); +}; + +GType ev_sidebar_get_type (void); +GtkWidget *ev_sidebar_new (void); +void ev_sidebar_add_page (EvSidebar *ev_sidebar, + const gchar *page_id, + const gchar *title, + GtkWidget *main_widget); +void ev_sidebar_set_current_page + (EvSidebar *ev_sidebar, + const char *page_id); +const char *ev_sidebar_get_current_page + (EvSidebar *ev_sidebar); + + +G_END_DECLS + +#endif /* __EV_SIDEBAR_H__ */ + + diff --git a/trunk/src/list_v4l.c b/trunk/src/list_v4l.c new file mode 100644 index 000000000..a01a9dc0f --- /dev/null +++ b/trunk/src/list_v4l.c @@ -0,0 +1,35 @@ + +#include <glib.h> +#include "video-dev.h" + +static void +list_v4l (void) +{ + GList *devs, *l; + VideoDev *dev; + + devs = scan_for_video_devices (); + + if (devs == NULL) + { + g_print ("No v4l devices\n"); + return; + } + + for (l = devs; l != NULL; l = l->next) + { + dev = l->data; + + g_print ("name: %s \ndevice: %s\n", + dev->display_name, dev->device); + if (l->next != NULL) + g_print ("\n"); + } +} + +int main (int argc, char **argv) +{ + list_v4l (); + return 0; +} + diff --git a/trunk/src/plparse/.cvsignore b/trunk/src/plparse/.cvsignore new file mode 100644 index 000000000..ee82fe0ec --- /dev/null +++ b/trunk/src/plparse/.cvsignore @@ -0,0 +1,7 @@ +Makefile +Makefile.in +stamp-totem-pl-parser-builtins.h +test-parser +totem-pl-parser-builtins.* +totem-pl-parser-features.h +totemplparser-marshal.* diff --git a/trunk/src/plparse/Makefile.am b/trunk/src/plparse/Makefile.am new file mode 100644 index 000000000..27df8202e --- /dev/null +++ b/trunk/src/plparse/Makefile.am @@ -0,0 +1,164 @@ +noinst_PROGRAMS = test-parser + +test_parser_SOURCES = test-parser.c + +test_parser_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir)/src/plparser \ + -DGNOMELOCALEDIR=\""$(datadir)/locale"\"\ + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +test_parser_CFLAGS = \ + $(EXTRA_GNOME_CFLAGS) \ + $(HAL_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(WARN_CFLAGS) \ + $(AM_CFLAGS) + +test_parser_LDADD = \ + $(EXTRA_GNOME_LIBS) \ + libtotem-plparser.la + +lib_LTLIBRARIES = libtotem-plparser.la +noinst_LTLIBRARIES = libtotem-plparser-mini.la + +MARSHALFILES = totemplparser-marshal.c totemplparser-marshal.h +BUILT_SOURCES = $(MARSHALFILES) + +totemplparser-marshal.c: totemplparser-marshal.h + ( $(GLIB_GENMARSHAL) --prefix=totemplparser_marshal $(srcdir)/totemplparser-marshal.list --header --body > totemplparser-marshal.c ) +totemplparser-marshal.h: totemplparser-marshal.list + ( $(GLIB_GENMARSHAL) --prefix=totemplparser_marshal $(srcdir)/totemplparser-marshal.list --header > totemplparser-marshal.h ) + +plparserincludedir = $(pkgincludedir)/1/plparser +plparserinclude_HEADERS = \ + totem-pl-parser-builtins.h \ + totem-pl-parser-features.h \ + totem-pl-parser.h \ + totem-disc.h + +libtotem_plparser_la_SOURCES = \ + totem-disc.c \ + totem-disc.h \ + totem-pl-parser-builtins.c \ + totem-pl-parser-builtins.h \ + totem-pl-parser.c \ + totem-pl-parser-features.h \ + totem-pl-parser.h \ + totem-pl-parser-lines.c \ + totem-pl-parser-lines.h \ + totemplparser-marshal.c \ + totemplparser-marshal.h \ + totem-pl-parser-media.c \ + totem-pl-parser-media.h \ + totem-pl-parser-misc.c \ + totem-pl-parser-misc.h \ + totem-pl-parser-pls.c \ + totem-pl-parser-pls.h \ + totem-pl-parser-private.h \ + totem-pl-parser-qt.c \ + totem-pl-parser-qt.h \ + totem-pl-parser-smil.c \ + totem-pl-parser-smil.h \ + totem-pl-parser-wm.c \ + totem-pl-parser-wm.h \ + totem-pl-parser-xspf.c \ + totem-pl-parser-xspf.h + +libtotem_plparser_la_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir)/src/plparser \ + -DGNOMELOCALEDIR=\""$(datadir)/locale"\"\ + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +libtotem_plparser_la_CFLAGS = \ + $(TOTEM_PLPARSER_CFLAGS) \ + $(HAL_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(WARN_CFLAGS) \ + $(AM_CFLAGS) + +libtotem_plparser_la_LIBADD = \ + $(TOTEM_PLPARSER_LIBS) \ + $(HAL_LIBS) + +libtotem_plparser_la_LDFLAGS = \ + -version-info $(PLPARSER_LT_VERSION) \ + -export-symbols $(srcdir)/plparser.symbols \ + -no-undefined \ + $(AM_LDFLAGS) + +libtotem_plparser_mini_la_SOURCES = \ + totem-pl-parser-mini.h \ + totem-pl-parser.c \ + totem-pl-parser-lines.c \ + totem-pl-parser-lines.h \ + totem-pl-parser-misc.c \ + totem-pl-parser-misc.h \ + totem-pl-parser-pls.c \ + totem-pl-parser-pls.h \ + totem-pl-parser-private.h \ + totem-pl-parser-qt.c \ + totem-pl-parser-qt.h \ + totem-pl-parser-smil.c \ + totem-pl-parser-smil.h \ + totem-pl-parser-wm.c \ + totem-pl-parser-wm.h \ + totem-pl-parser-xspf.c \ + totem-pl-parser-xspf.h + +libtotem_plparser_mini_la_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir)/src/plparser \ + -DTOTEM_PL_PARSER_MINI \ + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +libtotem_plparser_mini_la_CFLAGS = \ + $(TOTEM_PLPARSER_MINI_CFLAGS) \ + $(WARN_CFLAGS) \ + $(AM_CFLAGS) + +libtotem_plparser_mini_la_LIBADD = \ + $(TOTEM_PLPARSER_MINI_LIBS) + +libtotem_plparser_mini_la_LDFLAGS = \ + -no-undefined \ + $(AM_LDFLAGS) + +totem-pl-parser-builtins.h: stamp-totem-pl-parser-builtins.h + @true + +stamp-totem-pl-parser-builtins.h: totem-pl-parser.h Makefile + (cd $(srcdir) && $(GLIB_MKENUMS) \ + --fhead "#ifndef __TOTEM_PL_PARSER_BUILTINS_H__\n#define __TOTEM_PL_PARSER_BUILTINS_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ + --fprod "/* enumerations from \"@filename@\" */\n" \ + --vhead "GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define TOTEM_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ + --ftail "G_END_DECLS\n\n#endif /* __TOTEM_PL_PARSER_BUILTINS_H__ */" totem-pl-parser.h) >> xgen-gtbh \ + && (cmp -s xgen-gtbh totem-pl-parser-builtins.h || cp xgen-gtbh totem-pl-parser-builtins.h ) \ + && rm -f xgen-gtbh \ + && echo timestamp > $(@F) + +totem-pl-parser-builtins.c: totem-pl-parser.h Makefile totem-pl-parser-builtins.h + (cd $(srcdir) && $(GLIB_MKENUMS) \ + --fhead "#include \"totem-pl-parser.h\"\n#include \"totem-pl-parser-builtins.h\"" \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \ + totem-pl-parser.h ) > xgen-gtbc \ + && cp xgen-gtbc totem-pl-parser-builtins.c \ + && rm -f xgen-gtbc + +CLEANFILES = \ + totem-pl-parser-builtins.h \ + totem-pl-parser-builtins.c \ + stamp-totem-pl-parser-builtins.h \ + $(BUILT_SOURCES) + +EXTRA_DIST = \ + totemplparser-marshal.list \ + totem-pl-parser-features.h.in \ + plparser.symbols diff --git a/trunk/src/plparse/plparser.symbols b/trunk/src/plparse/plparser.symbols new file mode 100644 index 000000000..608fea84a --- /dev/null +++ b/trunk/src/plparse/plparser.symbols @@ -0,0 +1,18 @@ +totem_pl_parser_error_quark +totem_pl_parser_get_type +totem_pl_parser_write +totem_pl_parser_write_with_title +totem_pl_parser_add_ignored_scheme +totem_pl_parser_add_ignored_mimetype +totem_pl_parser_parse +totem_pl_parser_parse_with_base +totem_pl_parser_new +totem_pl_parser_can_parse_from_data +totem_pl_parser_can_parse_from_filename +totem_cd_detect_type +totem_cd_detect_type_with_url +totem_cd_detect_type_from_dir +totem_cd_get_human_readable_name +totem_cd_mrl_from_type +totem_cd_has_medium +totemplparser_marshal_VOID__STRING_STRING_STRING diff --git a/trunk/src/plparse/test-parser.c b/trunk/src/plparse/test-parser.c new file mode 100644 index 000000000..889825411 --- /dev/null +++ b/trunk/src/plparse/test-parser.c @@ -0,0 +1,316 @@ +#include "config.h" + +#include <locale.h> + +#include <glib.h> +#include <unistd.h> +#include <stdlib.h> +#include <libgnomevfs/gnome-vfs.h> + +#include "totem-pl-parser.h" +#include "totem-pl-parser-mini.h" + +#define USE_DATA + +static GMainLoop *loop = NULL; +static gboolean option_no_recurse = FALSE; +static gboolean option_debug = FALSE; +static gboolean option_data = FALSE; +static gboolean option_force = FALSE; +static gboolean option_disable_unsafe = FALSE; +static char *option_base_uri = NULL; +static char **files = NULL; + +static void +header (const char *message) +{ + g_print ("\n"); + g_print ("###################### %s ################\n", message); + g_print ("\n"); +} + +#if 0 +static void +test_relative_real (const char *url, const char *output) +{ + char *base, *dos; + + g_print ("url: %s\n", url); + g_print ("output: %s\n", output); + base = totem_pl_parser_relative (url, output); + if (base) { + g_print ("relative path: %s\n", base); + } else { + g_print ("no relative path\n"); + } + dos = totem_pl_parser_url_to_dos (url, output); + g_print ("DOS path: %s\n", dos); + g_print ("\n"); + + g_free (base); + g_free (dos); +} + +static void +test_relative (void) +{ + header ("relative"); + + test_relative_real ("/home/hadess/test/test file.avi", + "/home/hadess/foobar.m3u"); + test_relative_real ("file:///home/hadess/test/test%20file.avi", + "/home/hadess/whatever.m3u"); + test_relative_real ("smb://server/share/file.mp3", + "/home/hadess/whatever again.m3u"); + test_relative_real ("smb://server/share/file.mp3", + "smb://server/share/file.m3u"); + test_relative_real ("/home/hadess/test.avi", + "/home/hadess/test/file.m3u"); + test_relative_real ("http://foobar.com/test.avi", + "/home/hadess/test/file.m3u"); +} +#endif +static void +entry_added (TotemPlParser *parser, const char *uri, const char *title, + const char *genre, gpointer data) +{ + g_print ("added URI '%s' with title '%s' genre '%s'\n", uri, + title ? title : "empty", genre ? genre : "empty"); +} + +static void +test_parsing_real (TotemPlParser *pl, const char *url) +{ + TotemPlParserResult res; + + res = totem_pl_parser_parse_with_base (pl, url, option_base_uri, FALSE); + if (res != TOTEM_PL_PARSER_RESULT_SUCCESS) { + switch (res) { + case TOTEM_PL_PARSER_RESULT_UNHANDLED: + g_print ("url '%s' unhandled\n", url); + break; + case TOTEM_PL_PARSER_RESULT_ERROR: + g_print ("error handling url '%s'\n", url); + break; + case TOTEM_PL_PARSER_RESULT_IGNORED: + g_print ("ignored url '%s'\n", url); + break; + default: + g_assert_not_reached (); + ;; + } + } +} + +static gboolean +push_parser (gpointer data) +{ + TotemPlParser *pl = (TotemPlParser *)data; + + if (files != NULL) { + guint i; + + for (i = 0; files[i] != NULL; ++i) { + test_parsing_real (pl, files[i]); + } + } else { + //test_parsing_real (pl, "file:///mnt/cdrom"); + test_parsing_real (pl, "file:///home/hadess/Movies"); + /* Bugzilla 158052, 404 */ + test_parsing_real (pl, "http://live.hujjat.org:7860/main"); + /* Bugzilla 330120 */ + test_parsing_real (pl, "file:///tmp/file_doesnt_exist.wmv"); + /* Bugzilla 323683 */ + test_parsing_real (pl, "http://www.comedycentral.com/sitewide/media_player/videoswitcher.jhtml?showid=934&category=/shows/the_daily_show/videos/headlines&sec=videoId%3D36032%3BvideoFeatureId%3D%3BpoppedFrom%3D_shows_the_daily_show_index.jhtml%3BisIE%3Dfalse%3BisPC%3Dtrue%3Bpagename%3Dmedia_player%3Bzyg%3D%27%2Bif_nt_zyg%2B%27%3Bspan%3D%27%2Bif_nt_span%2B%27%3Bdemo%3D%27%2Bif_nt_demo%2B%27%3Bbps%3D%27%2Bif_nt_bandwidth%2B%27%3Bgateway%3Dshows%3Bsection_1%3Dthe_daily_show%3Bsection_2%3Dvideos%3Bsection_3%3Dheadlines%3Bzyg%3D%27%2Bif_nt_zyg%2B%27%3Bspan%3D%27%2Bif_nt_span%2B%27%3Bdemo%3D%27%2Bif_nt_demo%2B%27%3Bera%3D%27%2Bif_nt_era%2B%27%3Bbps%3D%27%2Bif_nt_bandwidth%2B%27%3Bfla%3D%27%2Bif_nt_Flash%2B%27&itemid=36032&clip=com/dailyshow/headlines/10156_headline.wmv&mswmext=.asx"); + } + g_main_loop_quit (loop); + return FALSE; +} + +#ifdef USE_DATA + +#define READ_CHUNK_SIZE 8192 +#define MIME_READ_CHUNK_SIZE 1024 + +static char * +test_data_get_data (const char *uri, guint *len) +{ + GnomeVFSResult result; + GnomeVFSHandle *handle; + char *buffer; + GnomeVFSFileSize total_bytes_read; + GnomeVFSFileSize bytes_read; + + *len = 0; + + /* Open the file. */ + result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ); + if (result != GNOME_VFS_OK) + return NULL; + + /* Read the whole thing, up to MIME_READ_CHUNK_SIZE */ + buffer = NULL; + total_bytes_read = 0; + do { + buffer = g_realloc (buffer, total_bytes_read + + MIME_READ_CHUNK_SIZE); + result = gnome_vfs_read (handle, + buffer + total_bytes_read, + MIME_READ_CHUNK_SIZE, + &bytes_read); + if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) { + g_free (buffer); + gnome_vfs_close (handle); + return NULL; + } + + /* Check for overflow. */ + if (total_bytes_read + bytes_read < total_bytes_read) { + g_free (buffer); + gnome_vfs_close (handle); + return NULL; + } + + total_bytes_read += bytes_read; + } while (result == GNOME_VFS_OK + && total_bytes_read < MIME_READ_CHUNK_SIZE); + + /* Close the file but don't overwrite the possible error */ + if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) + gnome_vfs_close (handle); + else + result = gnome_vfs_close (handle); + + if (result != GNOME_VFS_OK) { + g_message ("URL '%s' couldn't be read or closed in _get_mime_type_with_data: '%s'\n", uri, gnome_vfs_result_to_string (result)); + g_free (buffer); + return NULL; + } + + /* Return the file null-terminated. */ + buffer = g_realloc (buffer, total_bytes_read + 1); + buffer[total_bytes_read] = '\0'; + *len = total_bytes_read; + + return buffer; +} +#endif /* USE_DATA */ + +static void +test_data (void) +{ + guint i; + + for (i = 0; files[i] != NULL; ++i) { + char *filename = files[i]; + gboolean retval; +#ifdef USE_DATA + char *data; + guint len; + + data = test_data_get_data (filename, &len); + if (data == NULL) { + g_message ("Couldn't get data for %s", filename); + continue; + } + retval = totem_pl_parser_can_parse_from_data (data, len, TRUE); + g_free (data); +#else + retval = totem_pl_parser_can_parse_from_filename (filename, TRUE); +#endif /* USE_DATA */ + + if (retval != FALSE) { + g_message ("IS a playlist: %s", filename); + } else { + g_message ("ISNOT playlist: %s", filename); + } + } +} + +static void +playlist_started (TotemPlParser *parser, const char *title) +{ + g_message ("Playlist with name '%s' started", title); +} + +static void +playlist_ended (TotemPlParser *parser, const char *title) +{ + g_message ("Playlist with name '%s' ended", title); +} + +static void +test_parsing (void) +{ + TotemPlParser *pl = totem_pl_parser_new (); + + g_object_set (pl, "recurse", !option_no_recurse, + "debug", option_debug, + "force", option_force, + "disable-unsafe", option_disable_unsafe, + NULL); + g_signal_connect (G_OBJECT (pl), "entry", G_CALLBACK (entry_added), NULL); + g_signal_connect (G_OBJECT (pl), "playlist-start", G_CALLBACK (playlist_started), NULL); + g_signal_connect (G_OBJECT (pl), "playlist-end", G_CALLBACK (playlist_ended), NULL); + + header ("parsing"); + g_idle_add (push_parser, pl); + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); +} + +int main (int argc, char **argv) +{ + GOptionEntry option_entries [] = + { + { "no-recurse", 'n', 0, G_OPTION_ARG_NONE, &option_no_recurse, "Disable recursion", NULL }, + { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, "Enable debug", NULL }, + { "data", 't', 0, G_OPTION_ARG_NONE, &option_data, "Use data instead of filename", NULL }, + { "force", 'f', 0, G_OPTION_ARG_NONE, &option_force, "Force parsing", NULL }, + { "disable-unsafe", 'u', 0, G_OPTION_ARG_NONE, &option_disable_unsafe, "Disabling unsafe playlist-types", NULL }, + { "base-uri", 'b', 0, G_OPTION_ARG_STRING, &option_base_uri, "Base URI to resolve relative items from", NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &files, NULL, "[URI...]" }, + { NULL } + }; + GOptionContext *context; + GError *error = NULL; + gboolean retval; + + setlocale (LC_ALL, ""); + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, option_entries, NULL); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + + if (!retval) { + g_print ("Error parsing arguments: %s\n", error->message); + g_error_free (error); + + g_print ("Usage: %s <-n | --no-recurse> <-d | --debug> <-h | --help> <-t | --data > <-u | --disable-unsafe> <url>\n", argv[0]); + exit (1); + } + + gnome_vfs_init(); + + if (option_data != FALSE && files == NULL) { + g_message ("Please pass specific files to check by data"); + return 1; + } + + if (files == NULL) { +#if 0 + test_relative (); +#endif + test_parsing (); + } else { + if (option_data) { + test_data (); + } else { + test_parsing (); + } + } + + return 0; +} diff --git a/trunk/src/plparse/totem-disc.c b/trunk/src/plparse/totem-disc.c new file mode 100644 index 000000000..cbbc19014 --- /dev/null +++ b/trunk/src/plparse/totem-disc.c @@ -0,0 +1,925 @@ +/* Totem Disc Content Detection + * (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include <sys/stat.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <libgnomevfs/gnome-vfs.h> + +#ifdef HAVE_HAL +#include <libhal.h> +#include <dbus/dbus.h> +#endif + +#include "totem-disc.h" + +typedef struct _CdCache { + /* device node and mountpoint */ + char *device, *mountpoint; + GnomeVFSDrive *drive; + +#ifdef HAVE_HAL + LibHalContext *ctx; + /* If the disc is a media, have the UDI available here */ + char *disc_udi; +#endif + + /* capabilities of the device */ + int cap; + + /* Whether we have a medium */ + guint has_medium : 1; + /* if we're checking a media, or a dir */ + guint is_media : 1; + + /* indicates if we mounted this mountpoint ourselves or if it + * was already mounted. */ + guint self_mounted : 1; + guint mounted : 1; +} CdCache; + +/* + * Resolve relative paths + */ + +/* Copied from gtk+/gtk/gtkfilesystemunix.c */ + +/* If this was a publically exported function, it should return + * a dup'ed result, but we make it modify-in-place for efficiency + * here, and because it works for us. + */ +static void +canonicalize_filename (gchar *filename) +{ + gchar *p, *q; + gboolean last_was_slash = FALSE; + + p = filename; + q = filename; + + while (*p) + { + if (*p == G_DIR_SEPARATOR) + { + if (!last_was_slash) + *q++ = G_DIR_SEPARATOR; + + last_was_slash = TRUE; + } + else + { + if (last_was_slash && *p == '.') + { + if (*(p + 1) == G_DIR_SEPARATOR || + *(p + 1) == '\0') + { + if (*(p + 1) == '\0') + break; + + p += 1; + } + else if (*(p + 1) == '.' && + (*(p + 2) == G_DIR_SEPARATOR || + *(p + 2) == '\0')) + { + if (q > filename + 1) + { + q--; + while (q > filename + 1 && + *(q - 1) != G_DIR_SEPARATOR) + q--; + } + + if (*(p + 2) == '\0') + break; + + p += 2; + } + else + { + *q++ = *p; + last_was_slash = FALSE; + } + } + else + { + *q++ = *p; + last_was_slash = FALSE; + } + } + + p++; + } + + if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR) + q--; + + *q = '\0'; +} + +static char * +totem_resolve_symlink (const char *device, GError **error) +{ + char *dir, *link; + char *f; + char *f1; + + f = g_strdup (device); + while (g_file_test (f, G_FILE_TEST_IS_SYMLINK)) { + link = g_file_read_link (f, error); + if(link == NULL) { + g_free (f); + return NULL; + } + + dir = g_path_get_dirname (f); + f1 = g_build_filename (dir, link, NULL); + g_free (dir); + g_free (f); + f = f1; + } + + if (f != NULL) + canonicalize_filename (f); + return f; +} + +static gboolean +cd_cache_get_dev_from_volumes (GnomeVFSVolumeMonitor *mon, const char *device, + char **mountpoint) +{ + gboolean found; + GnomeVFSVolume *volume = NULL; + GList *list, *or; + + found = FALSE; + + for (or = list = gnome_vfs_volume_monitor_get_mounted_volumes (mon); + list != NULL; list = list->next) { + char *pdev, *pdev2; + + volume = list->data; + if (!(pdev = gnome_vfs_volume_get_device_path (volume))) + continue; + pdev2 = totem_resolve_symlink (pdev, NULL); + if (!pdev2) { + g_free (pdev); + continue; + } + g_free (pdev); + + if (strcmp (pdev2, device) == 0) { + char *mnt; + + mnt = gnome_vfs_volume_get_activation_uri (volume); + if (mnt && strncmp (mnt, "file://", 7) == 0) { + g_free (pdev2); + *mountpoint = g_strdup (mnt + 7); + g_free (mnt); + found = TRUE; + break; + } else if (mnt && strncmp (mnt, "cdda://", 7) == 0) { + g_free (pdev2); + *mountpoint = NULL; + g_free (mnt); + found = TRUE; + break; + } + g_free (mnt); + } + g_free (pdev2); + } + g_list_foreach (or, (GFunc) gnome_vfs_volume_unref, NULL); + g_list_free (or); + + return found; +} + +static gboolean +cd_cache_get_dev_from_drives (GnomeVFSVolumeMonitor *mon, const char *device, + char **mountpoint, GnomeVFSDrive **d) +{ + gboolean found; + GnomeVFSDrive *drive = NULL; + GList *list, *or; + + found = FALSE; + + for (or = list = gnome_vfs_volume_monitor_get_connected_drives (mon); + list != NULL; list = list->next) { + char *pdev, *pdev2; + + drive = list->data; + if (!(pdev = gnome_vfs_drive_get_device_path (drive))) + continue; + pdev2 = totem_resolve_symlink (pdev, NULL); + if (!pdev2) { + g_free (pdev); + continue; + } + g_free (pdev); + + if (strcmp (pdev2, device) == 0) { + char *mnt; + + mnt = gnome_vfs_drive_get_activation_uri (drive); + if (mnt && strncmp (mnt, "file://", 7) == 0) { + *mountpoint = g_strdup (mnt + 7); + } else { + *mountpoint = NULL; + } + found = TRUE; + g_free (pdev2); + g_free (mnt); + gnome_vfs_drive_ref (drive); + break; + } + g_free (pdev2); + } + g_list_foreach (or, (GFunc) gnome_vfs_drive_unref, NULL); + g_list_free (or); + + *d = drive; + + return found; +} + +#ifdef HAVE_HAL +static LibHalContext * +cd_cache_new_hal_ctx (void) +{ + LibHalContext *ctx; + DBusConnection *conn; + DBusError error; + + ctx = libhal_ctx_new (); + if (ctx == NULL) + return NULL; + + dbus_error_init (&error); + conn = dbus_bus_get_private (DBUS_BUS_SYSTEM, &error); + + if (conn != NULL && !dbus_error_is_set (&error)) { + if (!libhal_ctx_set_dbus_connection (ctx, conn)) { + libhal_ctx_free (ctx); + return NULL; + } + if (libhal_ctx_init (ctx, &error)) + return ctx; + } + + if (dbus_error_is_set (&error)) { + g_warning ("Couldn't get the system D-Bus: %s", error.message); + dbus_error_free (&error); + } + + libhal_ctx_free (ctx); + if (conn != NULL) + dbus_connection_unref (conn); + + return NULL; +} +#endif + +static CdCache * +cd_cache_new (const char *dev, + GError **error) +{ + CdCache *cache; + char *mountpoint = NULL, *device, *local; + GnomeVFSVolumeMonitor *mon; + GnomeVFSDrive *drive = NULL; +#ifdef HAVE_HAL + LibHalContext *ctx = NULL; +#endif + gboolean found; + + if (g_str_has_prefix (dev, "file://") != FALSE) + local = g_filename_from_uri (dev, NULL, NULL); + else + local = g_strdup (dev); + + g_assert (local != NULL); + + if (g_file_test (local, G_FILE_TEST_IS_DIR) != FALSE) { + cache = g_new0 (CdCache, 1); + cache->mountpoint = local; + cache->is_media = FALSE; + + return cache; + } + + /* retrieve mountpoint from gnome-vfs volumes and drives */ + device = totem_resolve_symlink (local, error); + g_free (local); + if (!device) + return NULL; + mon = gnome_vfs_get_volume_monitor (); + found = cd_cache_get_dev_from_drives (mon, device, &mountpoint, &drive); + if (!found) { + drive = NULL; + found = cd_cache_get_dev_from_volumes (mon, device, &mountpoint); + } + + if (!found) { + g_set_error (error, 0, 0, + _("Failed to find mountpoint for device %s"), + device); + return NULL; + } + +#ifdef HAVE_HAL + ctx = cd_cache_new_hal_ctx (); + if (!ctx) { + g_set_error (error, 0, 0, + _("Could not connect to the HAL daemon")); + return NULL; + } +#endif + + /* create struture */ + cache = g_new0 (CdCache, 1); + cache->device = device; + cache->mountpoint = mountpoint; + cache->self_mounted = FALSE; + cache->drive = drive; + cache->is_media = TRUE; +#ifdef HAVE_HAL + cache->ctx = ctx; +#endif + + return cache; +} + +#ifndef HAVE_HAL +static gboolean +cd_cache_has_medium (CdCache *cache) +{ + return TRUE; +} +#endif + +#ifdef HAVE_HAL +static gboolean +cd_cache_has_medium (CdCache *cache) +{ + char **devices; + int num_devices; + char *udi; + gboolean retval = FALSE; + DBusError error; + + if (cache->drive == NULL) + return FALSE; + + udi = gnome_vfs_drive_get_hal_udi (cache->drive); + if (udi == NULL) + return FALSE; + + dbus_error_init (&error); + devices = libhal_manager_find_device_string_match (cache->ctx, + "info.parent", udi, &num_devices, &error); + if (devices != NULL && num_devices >= 1) + retval = TRUE; + + if (dbus_error_is_set (&error)) { + g_warning ("Error getting the children: %s", error.message); + dbus_error_free (&error); + g_free (udi); + return FALSE; + } + + if (retval == FALSE) { + dbus_bool_t volume; + + if (libhal_device_property_exists (cache->ctx, + udi, "volume.is_disc", NULL) == FALSE) { + g_free (udi); + return FALSE; + } + + volume = libhal_device_get_property_bool (cache->ctx, + udi, "volume.is_disc", &error); + if (dbus_error_is_set (&error)) { + g_warning ("Error checking whether the volume is a disc: %s", + error.message); + dbus_error_free (&error); + g_free (udi); + return FALSE; + } + retval = TRUE; + cache->disc_udi = udi; + } else { + g_free (udi); + } + + if (devices != NULL) + libhal_free_string_array (devices); + + return retval; +} +#endif + +static gboolean +cd_cache_open_device (CdCache *cache, + GError **error) +{ + /* not a medium? */ + if (cache->is_media == FALSE || cache->has_medium != FALSE) { + return TRUE; + } + + if (cd_cache_has_medium (cache) == FALSE) { + g_set_error (error, 0, 0, + _("Please check that a disc is present in the drive.")); + return FALSE; + } + cache->has_medium = TRUE; + + return TRUE; +} + +typedef struct _CdCacheCallbackData { + CdCache *cache; + gboolean called; +} CdCacheCallbackData; + +static void +cb_mount_done (gboolean success, char * error, + char * detail, CdCacheCallbackData * data) +{ + data->called = TRUE; + data->cache->mounted = success != FALSE; +} + +static gboolean +cd_cache_open_mountpoint (CdCache *cache, + GError **error) +{ + CdCacheCallbackData data; + + /* already opened? */ + if (cache->mounted || cache->is_media == FALSE) + return TRUE; + + /* check for mounting - assume we'll mount ourselves */ + if (cache->drive == NULL) + return TRUE; + cache->self_mounted = !gnome_vfs_drive_is_mounted (cache->drive); + + /* mount if we have to */ + if (cache->self_mounted) { + /* mount - wait for callback */ + data.called = FALSE; + data.cache = cache; + gnome_vfs_drive_mount (cache->drive, + (GnomeVFSVolumeOpCallback) cb_mount_done, &data); + while (!data.called) g_main_context_iteration (NULL, TRUE); + + if (!cache->mounted) { + g_set_error (error, 0, 0, + _("Failed to mount %s"), cache->device); + return FALSE; + } + } + + if (!cache->mountpoint) { + GList *vol, *item; + + for (vol = item = gnome_vfs_drive_get_mounted_volumes (cache->drive); + item != NULL; item = item->next) { + char *mnt = gnome_vfs_volume_get_activation_uri (item->data); + + if (mnt && strncmp (mnt, "file://", 7) == 0) { + cache->mountpoint = g_strdup (mnt + 7); + g_free (mnt); + break; + } + g_free (mnt); + } + g_list_foreach (vol, (GFunc) gnome_vfs_volume_unref, NULL); + g_list_free (vol); + + if (!cache->mountpoint) { + g_set_error (error, 0, 0, + _("Failed to find mountpoint for %s"), cache->device); + return FALSE; + } + } + + return TRUE; +} + +static void +cb_umount_done (gboolean success, char * error, + char * detail, gboolean * called) +{ + *called = TRUE; +} + +static void +cd_cache_free (CdCache *cache) +{ + /* umount if we mounted */ + if (cache->self_mounted && cache->mounted) { + gboolean called = FALSE; + + gnome_vfs_drive_unmount (cache->drive, + (GnomeVFSVolumeOpCallback) cb_umount_done, &called); + while (!called) g_main_context_iteration (NULL, TRUE); + } + +#ifdef HAVE_HAL + if (cache->ctx != NULL) { + DBusConnection *conn; + + conn = libhal_ctx_get_dbus_connection (cache->ctx); + libhal_ctx_shutdown (cache->ctx, NULL); + libhal_ctx_free(cache->ctx); + /* Close the connection before doing the last unref */ + dbus_connection_close (conn); + dbus_connection_unref (conn); + + g_free (cache->disc_udi); + } +#endif /* HAVE_HAL */ + + /* free mem */ + if (cache->drive) + gnome_vfs_drive_unref (cache->drive); + g_free (cache->mountpoint); + g_free (cache->device); + g_free (cache); +} + +static MediaType +cd_cache_disc_is_cdda (CdCache *cache, + GError **error) +{ + MediaType type; + + /* We can't have audio CDs on disc, yet */ + if (cache->is_media == FALSE) + return MEDIA_TYPE_DATA; + if (!cd_cache_open_device (cache, error)) + return MEDIA_TYPE_ERROR; + +#ifdef HAVE_HAL + { + DBusError error; + dbus_bool_t is_cdda; + + dbus_error_init (&error); + + is_cdda = libhal_device_get_property_bool (cache->ctx, + cache->disc_udi, "volume.disc.has_audio", &error); + type = is_cdda ? MEDIA_TYPE_CDDA : MEDIA_TYPE_DATA; + + if (dbus_error_is_set (&error)) { + g_warning ("Error checking whether the volume is an audio CD: %s", + error.message); + dbus_error_free (&error); + return MEDIA_TYPE_ERROR; + } + return type; + } +#else + { + GList *vol, *item; + + type = MEDIA_TYPE_DATA; + + for (vol = item = gnome_vfs_drive_get_mounted_volumes (cache->drive); + item != NULL; item = item->next) { + char *mnt = gnome_vfs_volume_get_activation_uri (item->data); + if (mnt && strncmp (mnt, "cdda://", 7) == 0) { + g_free (mnt); + type = MEDIA_TYPE_CDDA; + break; + } + g_free (mnt); + } + g_list_foreach (vol, (GFunc) gnome_vfs_volume_unref, NULL); + g_list_free (vol); + } + + return type; +#endif +} + +static gboolean +cd_cache_file_exists (CdCache *cache, const char *subdir, const char *filename) +{ + char *path, *dir; + gboolean ret; + + dir = NULL; + + /* Check whether the directory exists, for a start */ + path = g_build_filename (cache->mountpoint, subdir, NULL); + ret = g_file_test (path, G_FILE_TEST_IS_DIR); + if (ret == FALSE) { + char *subdir_low; + + g_free (path); + subdir_low = g_ascii_strdown (subdir, -1); + path = g_build_filename (cache->mountpoint, subdir_low, NULL); + ret = g_file_test (path, G_FILE_TEST_IS_DIR); + g_free (path); + if (ret) { + dir = subdir_low; + } else { + g_free (subdir_low); + return FALSE; + } + } else { + g_free (path); + dir = g_strdup (subdir); + } + + /* And now the file */ + path = g_build_filename (cache->mountpoint, dir, filename, NULL); + ret = g_file_test (path, G_FILE_TEST_IS_REGULAR); + if (ret == FALSE) { + char *fname_low; + + g_free (path); + fname_low = g_ascii_strdown (filename, -1); + path = g_build_filename (cache->mountpoint, dir, fname_low, NULL); + ret = g_file_test (path, G_FILE_TEST_IS_REGULAR); + g_free (fname_low); + } + + g_free (dir); + g_free (path); + + return ret; +} + +static MediaType +cd_cache_disc_is_vcd (CdCache *cache, + GError **error) +{ + /* open disc and open mount */ + if (!cd_cache_open_device (cache, error)) + return MEDIA_TYPE_ERROR; + if (!cd_cache_open_mountpoint (cache, error)) + return MEDIA_TYPE_ERROR; + if (!cache->mountpoint) + return MEDIA_TYPE_ERROR; +#ifdef HAVE_HAL + if (cache->is_media != FALSE) { + DBusError error; + dbus_bool_t is_vcd; + + dbus_error_init (&error); + + is_vcd = libhal_device_get_property_bool (cache->ctx, + cache->disc_udi, "volume.disc.is_vcd", &error); + + if (dbus_error_is_set (&error)) { + g_warning ("Error checking whether the volume is a VCD: %s", + error.message); + dbus_error_free (&error); + return MEDIA_TYPE_ERROR; + } + if (is_vcd != FALSE) + return MEDIA_TYPE_VCD; + is_vcd = libhal_device_get_property_bool (cache->ctx, + cache->disc_udi, "volume.disc.is_svcd", &error); + + if (dbus_error_is_set (&error)) { + g_warning ("Error checking whether the volume is an SVCD: %s", + error.message); + dbus_error_free (&error); + return MEDIA_TYPE_ERROR; + } + return is_vcd ? MEDIA_TYPE_VCD : MEDIA_TYPE_DATA; + } +#endif + /* first is VCD, second is SVCD */ + if (cd_cache_file_exists (cache, "MPEGAV", "AVSEQ01.DAT") || + cd_cache_file_exists (cache, "MPEG2", "AVSEQ01.MPG")) + return MEDIA_TYPE_VCD; + + return MEDIA_TYPE_DATA; +} + +static MediaType +cd_cache_disc_is_dvd (CdCache *cache, + GError **error) +{ + /* open disc, check capabilities and open mount */ + if (!cd_cache_open_device (cache, error)) + return MEDIA_TYPE_ERROR; + if (!cd_cache_open_mountpoint (cache, error)) + return MEDIA_TYPE_ERROR; + if (!cache->mountpoint) + return MEDIA_TYPE_ERROR; +#ifdef HAVE_HAL + if (cache->is_media != FALSE) { + DBusError error; + dbus_bool_t is_dvd; + + dbus_error_init (&error); + + is_dvd = libhal_device_get_property_bool (cache->ctx, + cache->disc_udi, "volume.disc.is_videodvd", &error); + + if (dbus_error_is_set (&error)) { + g_warning ("Error checking whether the volume is a DVD: %s", + error.message); + dbus_error_free (&error); + return MEDIA_TYPE_ERROR; + } + return is_dvd ? MEDIA_TYPE_DVD : MEDIA_TYPE_DATA; + } +#endif + if (cd_cache_file_exists (cache, "VIDEO_TS", "VIDEO_TS.IFO")) + return MEDIA_TYPE_DVD; + + return MEDIA_TYPE_DATA; +} + +char * +totem_cd_mrl_from_type (const char *scheme, const char *dir) +{ + char *retval; + + if (g_str_has_prefix (dir, "file://") != FALSE) { + char *local; + local = g_filename_from_uri (dir, NULL, NULL); + retval = g_strdup_printf ("%s://%s", scheme, local); + g_free (local); + } else { + retval = g_strdup_printf ("%s://%s", scheme, dir); + } + return retval; +} + +MediaType +totem_cd_detect_type_from_dir (const char *dir, char **url, GError **error) +{ + CdCache *cache; + MediaType type; + + g_return_val_if_fail (dir != NULL, MEDIA_TYPE_ERROR); + + if (dir[0] != '/' && g_str_has_prefix (dir, "file://") == FALSE) + return MEDIA_TYPE_ERROR; + + if (!(cache = cd_cache_new (dir, error))) + return MEDIA_TYPE_ERROR; + if ((type = cd_cache_disc_is_vcd (cache, error)) == MEDIA_TYPE_DATA && + (type = cd_cache_disc_is_dvd (cache, error)) == MEDIA_TYPE_DATA) { + /* crap, nothing found */ + cd_cache_free (cache); + return type; + } + cd_cache_free (cache); + + if (url == NULL) { + return type; + } + + if (type == MEDIA_TYPE_DVD) { + *url = totem_cd_mrl_from_type ("dvd", dir); + } else if (type == MEDIA_TYPE_VCD) { + *url = totem_cd_mrl_from_type ("vcd", dir); + } + + return type; +} + +MediaType +totem_cd_detect_type_with_url (const char *device, + char **url, + GError **error) +{ + CdCache *cache; + MediaType type; + + if (url != NULL) + *url = NULL; + + if (!(cache = cd_cache_new (device, error))) + return MEDIA_TYPE_ERROR; + + type = cd_cache_disc_is_cdda (cache, error); + if (type == MEDIA_TYPE_ERROR && *error != NULL) { + cd_cache_free (cache); + return type; + } + + if ((type == MEDIA_TYPE_DATA || type == MEDIA_TYPE_ERROR) && + (type = cd_cache_disc_is_vcd (cache, error)) == MEDIA_TYPE_DATA && + (type = cd_cache_disc_is_dvd (cache, error)) == MEDIA_TYPE_DATA) { + /* crap, nothing found */ + } + + if (url == NULL) { + cd_cache_free (cache); + return type; + } + + switch (type) { + case MEDIA_TYPE_DVD: + *url = totem_cd_mrl_from_type ("dvd", device); + break; + case MEDIA_TYPE_VCD: + *url = totem_cd_mrl_from_type ("vcd", device); + break; + case MEDIA_TYPE_CDDA: + *url = totem_cd_mrl_from_type ("cdda", device); + break; + case MEDIA_TYPE_DATA: + *url = g_strdup (cache->mountpoint); + break; + default: + break; + } + + cd_cache_free (cache); + + return type; +} + +MediaType +totem_cd_detect_type (const char *device, + GError **error) +{ + return totem_cd_detect_type_with_url (device, NULL, error); +} + +gboolean +totem_cd_has_medium (const char *device) +{ + CdCache *cache; + gboolean retval = TRUE; + + if (!(cache = cd_cache_new (device, NULL))) + return TRUE; + + retval = cd_cache_has_medium (cache); + cd_cache_free (cache); + + return retval; +} + +const char * +totem_cd_get_human_readable_name (MediaType type) +{ + switch (type) + { + case MEDIA_TYPE_CDDA: + return N_("Audio CD"); + case MEDIA_TYPE_VCD: + return N_("Video CD"); + case MEDIA_TYPE_DVD: + return N_("DVD"); + default: + g_assert_not_reached (); + } + + return NULL; +} + +/* + * vim: sw=2 ts=8 cindent noai bs=2 + */ diff --git a/trunk/src/plparse/totem-disc.h b/trunk/src/plparse/totem-disc.h new file mode 100644 index 000000000..003e57bd7 --- /dev/null +++ b/trunk/src/plparse/totem-disc.h @@ -0,0 +1,51 @@ +/* Totem Disc Content Detection + * (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * totem-disc.h: media content detection + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef TOTEM_DISC_H +#define TOTEM_DISC_H + +#include <glib.h> + +G_BEGIN_DECLS + +typedef enum { + MEDIA_TYPE_ERROR = -1, /* error */ + MEDIA_TYPE_DATA = 1, + MEDIA_TYPE_CDDA, + MEDIA_TYPE_VCD, + MEDIA_TYPE_DVD +} MediaType; + +MediaType totem_cd_detect_type (const char *device, + GError **error); +MediaType totem_cd_detect_type_with_url (const char *device, + char **url, + GError **error); +MediaType totem_cd_detect_type_from_dir (const char *dir, + char **url, + GError **error); +const char * totem_cd_get_human_readable_name (MediaType type); +char * totem_cd_mrl_from_type (const char *scheme, const char *dir); +gboolean totem_cd_has_medium (const char *device); + +G_END_DECLS + +#endif /* TOTEM_DISC_H */ diff --git a/trunk/src/plparse/totem-pl-parser-features.h.in b/trunk/src/plparse/totem-pl-parser-features.h.in new file mode 100644 index 000000000..c739765f1 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-features.h.in @@ -0,0 +1,42 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + * + * Copyright (C) 2006 William Jon McCann <mccann@jhu.edu> + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef __TOTEM_PL_PARSER_VERSION_H__ +#define __TOTEM_PL_PARSER_VERSION_H__ + +/* compile time version + */ +#define TOTEM_PL_PARSER_VERSION_MAJOR (@TOTEM_PL_PARSER_VERSION_MAJOR@) +#define TOTEM_PL_PARSER_VERSION_MINOR (@TOTEM_PL_PARSER_VERSION_MINOR@) +#define TOTEM_PL_PARSER_VERSION_MICRO (@TOTEM_PL_PARSER_VERSION_MICRO@) + +/* check whether a version equal to or greater than + * major.minor.micro is present. + */ +#define TOTEM_PL_PARSER_CHECK_VERSION(major,minor,micro) \ + (TOTEM_PL_PARSER_VERSION_MAJOR > (major) || \ + (TOTEM_PL_PARSER_VERSION_MAJOR == (major) && TOTEM_PL_PARSER_VERSION_MINOR > (minor)) || \ + (TOTEM_PL_PARSER_VERSION_MAJOR == (major) && TOTEM_PL_PARSER_VERSION_MINOR == (minor) && \ + TOTEM_PL_PARSER_VERSION_MICRO >= (micro))) + + +#endif /* __TOTEM_PL_PARSER_VERSION_H__ */ + diff --git a/trunk/src/plparse/totem-pl-parser-lines.c b/trunk/src/plparse/totem-pl-parser-lines.c new file mode 100644 index 000000000..eae78da1d --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-lines.c @@ -0,0 +1,415 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <glib.h> + +#ifndef TOTEM_PL_PARSER_MINI +#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#include "totem-pl-parser-pls.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-lines.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI + +#define EXTINF "#EXTINF:" + +static char * +totem_pl_parser_url_to_dos (const char *url, const char *output) +{ + char *retval, *i; + + retval = totem_pl_parser_relative (url, output); + + if (retval == NULL) + retval = g_strdup (url); + + /* Don't change URIs, but change smb:// */ + if (g_str_has_prefix (retval, "smb://") != FALSE) + { + char *tmp; + tmp = g_strdup (retval + strlen ("smb:")); + g_free (retval); + retval = tmp; + } + + if (strstr (retval, "://") != NULL) + return retval; + + i = retval; + while (*i != '\0') + { + if (*i == '/') + *i = '\\'; + i++; + } + + return retval; +} + +gboolean +totem_pl_parser_write_m3u (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, const char *output, + gboolean dos_compatible, gpointer user_data, GError **error) +{ + GnomeVFSHandle *handle; + GnomeVFSResult res; + int num_entries_total, i; + gboolean success; + char *buf; + char *cr; + + res = gnome_vfs_open (&handle, output, GNOME_VFS_OPEN_WRITE); + if (res == GNOME_VFS_ERROR_NOT_FOUND) { + res = gnome_vfs_create (&handle, output, + GNOME_VFS_OPEN_WRITE, FALSE, + GNOME_VFS_PERM_USER_WRITE + | GNOME_VFS_PERM_USER_READ + | GNOME_VFS_PERM_GROUP_READ); + } + + if (res != GNOME_VFS_OK) { + g_set_error(error, + TOTEM_PL_PARSER_ERROR, + TOTEM_PL_PARSER_ERROR_VFS_OPEN, + _("Couldn't open file '%s': %s"), + output, gnome_vfs_result_to_string (res)); + return FALSE; + } + + cr = dos_compatible ? "\r\n" : "\n"; + num_entries_total = gtk_tree_model_iter_n_children (model, NULL); + if (num_entries_total == 0) + return TRUE; + + for (i = 1; i <= num_entries_total; i++) { + GtkTreeIter iter; + char *url, *title, *path2; + gboolean custom_title; + + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, i - 1) == FALSE) + continue; + + func (model, &iter, &url, &title, &custom_title, user_data); + + if (totem_pl_parser_scheme_is_ignored (parser, url) != FALSE) + { + g_free (url); + g_free (title); + continue; + } + + if (custom_title != FALSE) { + buf = g_strdup_printf (EXTINF",%s%s", title, cr); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + if (success == FALSE) { + g_free (title); + g_free (url); + gnome_vfs_close (handle); + return FALSE; + } + } + g_free (title); + + if (dos_compatible == FALSE) { + char *tmp; + tmp = totem_pl_parser_relative (url, output); + if (tmp == NULL && g_str_has_prefix (url, "file:")) { + path2 = g_filename_from_uri (url, NULL, NULL); + } else { + path2 = tmp; + } + } else { + path2 = totem_pl_parser_url_to_dos (url, output); + } + + buf = g_strdup_printf ("%s%s", path2 ? path2 : url, cr); + g_free (path2); + g_free (url); + + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + + if (success == FALSE) + { + gnome_vfs_close (handle); + return FALSE; + } + } + + gnome_vfs_close (handle); + + return TRUE; +} + +TotemPlParserResult +totem_pl_parser_add_ram (TotemPlParser *parser, const char *url, gpointer data) +{ + gboolean retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + char *contents, **lines; + int size, i; + const char *split_char; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + /* figure out whether we're a unix or dos RAM file */ + if (strstr(contents,"\x0d") == NULL) + split_char = "\n"; + else + split_char = "\x0d\n"; + + lines = g_strsplit (contents, split_char, 0); + g_free (contents); + + for (i = 0; lines[i] != NULL; i++) { + /* Empty line */ + if (totem_pl_parser_line_is_empty (lines[i]) != FALSE) + continue; + + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + /* Either it's a URI, or it has a proper path ... */ + if (strstr(lines[i], "://") != NULL + || lines[i][0] == G_DIR_SEPARATOR) { + /* .ram files can contain .smil entries */ + if (totem_pl_parser_parse_internal (parser, lines[i], NULL) != TOTEM_PL_PARSER_RESULT_SUCCESS) + { + totem_pl_parser_add_one_url (parser, + lines[i], NULL); + } + } else if (strcmp (lines[i], "--stop--") == 0) { + /* For Real Media playlists, handle the stop command */ + break; + } else { + char *base; + + /* Try with a base */ + base = totem_pl_parser_base_url (url); + + if (totem_pl_parser_parse_internal (parser, lines[i], base) != TOTEM_PL_PARSER_RESULT_SUCCESS) + { + char *fullpath; + fullpath = g_strdup_printf ("%s/%s", base, lines[i]); + totem_pl_parser_add_one_url (parser, fullpath, NULL); + g_free (fullpath); + } + g_free (base); + } + } + + g_strfreev (lines); + + return retval; +} + +static const char * +totem_pl_parser_get_extinfo_title (gboolean extinfo, char **lines, int i) +{ + const char *extinf, *sep; + + if (extinfo == FALSE || lines == NULL || i <= 0) + return NULL; + + /* It's bound to have an EXTINF if we have extinfo */ + extinf = lines[i-1] + strlen(EXTINF); + if (extinf[0] == '\0') + return NULL; + + /* Handle ':' as a field separator */ + sep = strstr (extinf, ":"); + if (sep != NULL && sep[1] != '\0') { + sep++; + return sep; + } + + /* Handle ',' as a field separator */ + sep = strstr (extinf, ","); + if (sep == NULL || sep[1] == '\0') { + if (extinf[1] == '\0') + return NULL; + return extinf; + } + + sep++; + return sep; +} + +TotemPlParserResult +totem_pl_parser_add_m3u (TotemPlParser *parser, const char *url, + const char *_base, gpointer data) +{ + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + char *contents, **lines; + int size, i; + const char *split_char; + gboolean extinfo; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + /* .pls files with a .m3u extension, the nasties */ + if (g_str_has_prefix (contents, "[playlist]") != FALSE + || g_str_has_prefix (contents, "[Playlist]") != FALSE + || g_str_has_prefix (contents, "[PLAYLIST]") != FALSE) { + retval = totem_pl_parser_add_pls_with_contents (parser, url, _base, contents); + g_free (contents); + return retval; + } + + /* is TRUE if there's an EXTINF on the previous line */ + extinfo = FALSE; + + /* figure out whether we're a unix m3u or dos m3u */ + if (strstr(contents,"\x0d") == NULL) + split_char = "\n"; + else + split_char = "\x0d\n"; + + lines = g_strsplit (contents, split_char, 0); + g_free (contents); + + for (i = 0; lines[i] != NULL; i++) { + if (lines[i][0] == '\0') + continue; + + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + /* Ignore comments, but mark it if we have extra info */ + if (lines[i][0] == '#') { + extinfo = g_str_has_prefix (lines[i], EXTINF); + continue; + } + + /* Either it's a URI, or it has a proper path ... */ + if (strstr(lines[i], "://") != NULL + || lines[i][0] == G_DIR_SEPARATOR) { + if (totem_pl_parser_parse_internal (parser, lines[i], NULL) != TOTEM_PL_PARSER_RESULT_SUCCESS) { + totem_pl_parser_add_one_url (parser, lines[i], + totem_pl_parser_get_extinfo_title (extinfo, lines, i)); + } + extinfo = FALSE; + } else if (lines[i][0] == '\\' && lines[i][1] == '\\') { + /* ... Or it's in the windows smb form + * (\\machine\share\filename), Note drive names + * (C:\ D:\ etc) are unhandled (unknown base for + * drive letters) */ + char *tmpurl; + + lines[i] = g_strdelimit (lines[i], "\\", '/'); + tmpurl = g_strjoin (NULL, "smb:", lines[i], NULL); + + totem_pl_parser_add_one_url (parser, lines[i], + totem_pl_parser_get_extinfo_title (extinfo, lines, i)); + extinfo = FALSE; + + g_free (tmpurl); + } else { + /* Try with a base */ + char *fullpath, *base, sep; + + base = totem_pl_parser_base_url (url); + sep = (split_char[0] == '\n' ? '/' : '\\'); + if (sep == '\\') + lines[i] = g_strdelimit (lines[i], "\\", '/'); + fullpath = g_strdup_printf ("%s/%s", base, lines[i]); + totem_pl_parser_add_one_url (parser, fullpath, + totem_pl_parser_get_extinfo_title (extinfo, lines, i)); + g_free (fullpath); + g_free (base); + extinfo = FALSE; + } + } + + g_strfreev (lines); + + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_ra (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + if (data == NULL || totem_pl_parser_is_uri_list (data, strlen (data)) == FALSE) { + totem_pl_parser_add_one_url (parser, url, NULL); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + return totem_pl_parser_add_ram (parser, url, NULL); +} + + +#endif /* !TOTEM_PL_PARSER_MINI */ + +#define CHECK_LEN if (i >= len) { return FALSE; } + +gboolean +totem_pl_parser_is_uri_list (const char *data, gsize len) +{ + guint i = 0; + + /* Find the first bits of text */ + while (data[i] == '\n' || data[i] == '\t' || data[i] == ' ') { + i++; + CHECK_LEN; + } + CHECK_LEN; + + /* scheme always starts with a letter */ + if (g_ascii_isalpha (data[i]) == FALSE) + return FALSE; + while (g_ascii_isalnum (data[i]) != FALSE) { + i++; + CHECK_LEN; + } + + CHECK_LEN; + + /* First non-alphanum character should be a ':' */ + if (data[i] != ':') + return FALSE; + i++; + CHECK_LEN; + + if (data[i] != '/') + return FALSE; + i++; + CHECK_LEN; + + if (data[i] != '/') + return FALSE; + + return TRUE; +} + diff --git a/trunk/src/plparse/totem-pl-parser-lines.h b/trunk/src/plparse/totem-pl-parser-lines.h new file mode 100644 index 000000000..01b864716 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-lines.h @@ -0,0 +1,59 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_LINES_H +#define TOTEM_PL_PARSER_LINES_H + +G_BEGIN_DECLS + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#else +#include "totem-pl-parser-mini.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +gboolean totem_pl_parser_is_uri_list (const char *data, gsize len); + +#ifndef TOTEM_PL_PARSER_MINI +gboolean totem_pl_parser_write_m3u (TotemPlParser *parser, + GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, + gboolean dos_compatible, + gpointer user_data, + GError **error); +TotemPlParserResult totem_pl_parser_add_ram (TotemPlParser *parser, + const char *url, + gpointer data); +TotemPlParserResult totem_pl_parser_add_m3u (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +TotemPlParserResult totem_pl_parser_add_ra (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_LINES_H */ diff --git a/trunk/src/plparse/totem-pl-parser-media.c b/trunk/src/plparse/totem-pl-parser-media.c new file mode 100644 index 000000000..4d73f2b1b --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-media.c @@ -0,0 +1,272 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + + +#ifndef TOTEM_PL_PARSER_MINI +#include <string.h> +#include <glib.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#include "totem-disc.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-media.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI +/* Returns NULL if we don't have an ISO image, + * or an empty string if it's non-UTF-8 data */ +static char * +totem_pl_parser_iso_get_title (const char *url) +{ + char *fname; + FILE *file; +#define BUFFER_SIZE 128 + char buf [BUFFER_SIZE+1]; + int res; + char *str; + + fname = g_filename_from_uri (url, NULL, NULL); + if (fname == NULL) + return NULL; + + file = fopen (fname, "rb"); + if (file == NULL) + return NULL; + + /* Verify we have an ISO image */ + /* This check is for the raw sector images */ + res = fseek (file, 37633L, SEEK_SET); + if (res != 0) { + fclose (file); + return NULL; + } + + res = fread (buf, sizeof (char), 5, file); + if (res != 5 || strncmp (buf, "CD001", 5) != 0) { + /* Standard ISO images */ + res = fseek (file, 32769L, SEEK_SET); + if (res != 0) { + fclose (file); + return NULL; + } + res = fread (buf, sizeof (char), 5, file); + if (res != 5 || strncmp (buf, "CD001", 5) != 0) { + /* High Sierra images */ + res = fseek (file, 32776L, SEEK_SET); + if (res != 0) { + fclose (file); + return NULL; + } + res = fread (buf, sizeof (char), 5, file); + if (res != 5 || strncmp (buf, "CDROM", 5) != 0) { + fclose (file); + return NULL; + } + } + } + /* Extract the volume label from the image */ + res = fseek (file, 32808L, SEEK_SET); + if (res != 0) { + fclose (file); + return NULL; + } + res = fread (buf, sizeof(char), BUFFER_SIZE, file); + fclose (file); + if (res != BUFFER_SIZE) + return NULL; + + buf [BUFFER_SIZE] = '\0'; + str = g_strdup (g_strstrip (buf)); + if (!g_utf8_validate (str, -1, NULL)) { + g_free (str); + return g_strdup (""); + } + + return str; +} + +TotemPlParserResult +totem_pl_parser_add_iso (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + GnomeVFSFileInfo *info; + char *item, *label; + + /* This is a hack, it could be a VCD or DVD */ + if (g_str_has_prefix (url, "file://") == FALSE) + return TOTEM_PL_PARSER_RESULT_IGNORED; + + label = totem_pl_parser_iso_get_title (url); + if (label == NULL) { + /* Not an ISO image */ + return TOTEM_PL_PARSER_RESULT_UNHANDLED; + } + if (label[0] == '\0') { + g_free (label); + label = NULL; + } + + info = gnome_vfs_file_info_new (); + if (gnome_vfs_get_file_info (url, info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS) != GNOME_VFS_OK) { + gnome_vfs_file_info_unref (info); + return TOTEM_PL_PARSER_RESULT_IGNORED; + } + + /* Less than 700 megs, and it's a VCD */ + if (info->size < 700 * 1024 * 1024) { + item = totem_cd_mrl_from_type ("vcd", url); + } else { + item = totem_cd_mrl_from_type ("dvd", url); + } + + gnome_vfs_file_info_unref (info); + + totem_pl_parser_add_one_url (parser, item, label); + g_free (label); + g_free (item); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +TotemPlParserResult +totem_pl_parser_add_cue (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + char *vcdurl; + + vcdurl = totem_cd_mrl_from_type ("vcd", url); + totem_pl_parser_add_one_url (parser, vcdurl, NULL); + g_free (vcdurl); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +static int +totem_pl_parser_dir_compare (GnomeVFSFileInfo *a, GnomeVFSFileInfo *b) +{ + if (a->name == NULL) { + if (b->name == NULL) + return 0; + else + return -1; + } else { + if (b->name == NULL) + return 1; + else + return strcmp (a->name, b->name); + } +} + +TotemPlParserResult +totem_pl_parser_add_directory (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + MediaType type; + GList *list, *l; + GnomeVFSResult res; + char *media_url; + + type = totem_cd_detect_type_from_dir (url, &media_url, NULL); + if (type != MEDIA_TYPE_DATA && type != MEDIA_TYPE_ERROR) { + if (media_url != NULL) { + char *basename = NULL, *fname; + + fname = g_filename_from_uri (url, NULL, NULL); + if (fname != NULL) { + basename = g_filename_display_basename (fname); + g_free (fname); + } + totem_pl_parser_add_one_url (parser, media_url, basename); + g_free (basename); + g_free (media_url); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + } + + res = gnome_vfs_directory_list_load (&list, url, + GNOME_VFS_FILE_INFO_DEFAULT); + if (res != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + list = g_list_sort (list, (GCompareFunc) totem_pl_parser_dir_compare); + l = list; + + while (l != NULL) { + char *name, *fullpath; + GnomeVFSFileInfo *info = l->data; + TotemPlParserResult ret; + + if (info->name != NULL && (strcmp (info->name, ".") == 0 + || strcmp (info->name, "..") == 0)) { + l = l->next; + continue; + } + + name = gnome_vfs_escape_string (info->name); + fullpath = g_strconcat (url, "/", name, NULL); + g_free (name); + + ret = totem_pl_parser_parse_internal (parser, fullpath, NULL); + if (ret != TOTEM_PL_PARSER_RESULT_SUCCESS && ret != TOTEM_PL_PARSER_RESULT_IGNORED) + totem_pl_parser_add_one_url (parser, fullpath, NULL); + + l = l->next; + } + + g_list_foreach (list, (GFunc) gnome_vfs_file_info_unref, NULL); + g_list_free (list); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +TotemPlParserResult +totem_pl_parser_add_block (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + MediaType type; + char *media_url; + GError *err = NULL; + + type = totem_cd_detect_type_with_url (url, &media_url, &err); + if (err != NULL) + DEBUG(g_print ("Couldn't get CD type for URL '%s': %s\n", url, err->message)); + if (type == MEDIA_TYPE_DATA || media_url == NULL) + return TOTEM_PL_PARSER_RESULT_UNHANDLED; + else if (type == MEDIA_TYPE_ERROR) + return TOTEM_PL_PARSER_RESULT_ERROR; + + totem_pl_parser_add_one_url (parser, media_url, NULL); + g_free (media_url); + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + + diff --git a/trunk/src/plparse/totem-pl-parser-media.h b/trunk/src/plparse/totem-pl-parser-media.h new file mode 100644 index 000000000..2f9056652 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-media.h @@ -0,0 +1,53 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_MEDIA_H +#define TOTEM_PL_PARSER_MEDIA_H + +G_BEGIN_DECLS + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#ifndef TOTEM_PL_PARSER_MINI +TotemPlParserResult totem_pl_parser_add_iso (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +TotemPlParserResult totem_pl_parser_add_cue (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +TotemPlParserResult totem_pl_parser_add_directory (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +TotemPlParserResult totem_pl_parser_add_block (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_MEDIA_H */ diff --git a/trunk/src/plparse/totem-pl-parser-mini.h b/trunk/src/plparse/totem-pl-parser-mini.h new file mode 100644 index 000000000..309e0d592 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-mini.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_MINI_H +#define TOTEM_PL_PARSER_MINI_H + +#include <glib.h> + +G_BEGIN_DECLS + +gboolean totem_pl_parser_can_parse_from_data (const char *data, + gsize len, + gboolean debug); +gboolean totem_pl_parser_can_parse_from_filename (const char *filename, + gboolean debug); + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_MINI_H */ diff --git a/trunk/src/plparse/totem-pl-parser-misc.c b/trunk/src/plparse/totem-pl-parser-misc.c new file mode 100644 index 000000000..97415db91 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-misc.c @@ -0,0 +1,137 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <glib.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> + +#ifndef TOTEM_PL_PARSER_MINI +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnomevfs/gnome-vfs-utils.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#include "totem-disc.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-misc.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI +TotemPlParserResult +totem_pl_parser_add_gvp (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + char *contents, **lines, *title, *link, *version; + int size; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + if (g_str_has_prefix (contents, "#.download.the.free.Google.Video.Player") == FALSE && g_str_has_prefix (contents, "# download the free Google Video Player") == FALSE) { + g_free (contents); + return retval; + } + + lines = g_strsplit (contents, "\n", 0); + g_free (contents); + + /* We only handle GVP version 1.1 for now */ + version = totem_pl_parser_read_ini_line_string_with_sep (lines, "gvp_version", FALSE, ":"); + if (version == NULL || strcmp (version, "1.1") != 0) { + g_free (version); + g_strfreev (lines); + return retval; + } + g_free (version); + + link = totem_pl_parser_read_ini_line_string_with_sep (lines, "url", FALSE, ":"); + if (link == NULL) { + g_strfreev (lines); + return retval; + } + + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + title = totem_pl_parser_read_ini_line_string_with_sep (lines, "title", FALSE, ":"); + + totem_pl_parser_add_one_url (parser, link, title); + + g_free (link); + g_free (title); + g_strfreev (lines); + + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_desktop (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + char *contents, **lines; + const char *path, *display_name, *type; + int size; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + lines = g_strsplit (contents, "\n", 0); + g_free (contents); + + type = totem_pl_parser_read_ini_line_string (lines, "Type", FALSE); + if (type == NULL || g_ascii_strcasecmp (type, "Link") != 0) { + g_strfreev (lines); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + path = totem_pl_parser_read_ini_line_string (lines, "URL", FALSE); + if (path == NULL) { + g_strfreev (lines); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + display_name = totem_pl_parser_read_ini_line_string (lines, "Name", FALSE); + + if (totem_pl_parser_ignore (parser, path) == FALSE) { + totem_pl_parser_add_one_url (parser, path, display_name); + } else { + if (totem_pl_parser_parse_internal (parser, path, NULL) != TOTEM_PL_PARSER_RESULT_SUCCESS) + totem_pl_parser_add_one_url (parser, path, display_name); + } + + g_strfreev (lines); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + + diff --git a/trunk/src/plparse/totem-pl-parser-misc.h b/trunk/src/plparse/totem-pl-parser-misc.h new file mode 100644 index 000000000..f452228cb --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-misc.h @@ -0,0 +1,47 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_MISC_H +#define TOTEM_PL_PARSER_MISC_H + +G_BEGIN_DECLS + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#else +#include "totem-pl-parser-mini.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#ifndef TOTEM_PL_PARSER_MINI +TotemPlParserResult totem_pl_parser_add_gvp (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +TotemPlParserResult totem_pl_parser_add_desktop (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_MISC_H */ diff --git a/trunk/src/plparse/totem-pl-parser-pls.c b/trunk/src/plparse/totem-pl-parser-pls.c new file mode 100644 index 000000000..4a253eeca --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-pls.c @@ -0,0 +1,311 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef TOTEM_PL_PARSER_MINI +#include <string.h> +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-pls.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI +gboolean +totem_pl_parser_write_pls (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, const char *title, + gpointer user_data, GError **error) +{ + GnomeVFSHandle *handle; + GnomeVFSResult res; + int num_entries_total, num_entries, i; + char *buf; + gboolean success; + + num_entries = totem_pl_parser_num_entries (parser, model, func, user_data); + num_entries_total = gtk_tree_model_iter_n_children (model, NULL); + + res = gnome_vfs_open (&handle, output, GNOME_VFS_OPEN_WRITE); + if (res == GNOME_VFS_ERROR_NOT_FOUND) { + res = gnome_vfs_create (&handle, output, + GNOME_VFS_OPEN_WRITE, FALSE, + GNOME_VFS_PERM_USER_WRITE + | GNOME_VFS_PERM_USER_READ + | GNOME_VFS_PERM_GROUP_READ); + } + + if (res != GNOME_VFS_OK) { + g_set_error(error, + TOTEM_PL_PARSER_ERROR, + TOTEM_PL_PARSER_ERROR_VFS_OPEN, + _("Couldn't open file '%s': %s"), + output, gnome_vfs_result_to_string (res)); + return FALSE; + } + + buf = g_strdup ("[playlist]\n"); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + if (success == FALSE) + return FALSE; + + if (title != NULL) { + buf = g_strdup_printf ("X-GNOME-Title=%s\n", title); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + if (success == FALSE) + { + gnome_vfs_close (handle); + return FALSE; + } + } + + buf = g_strdup_printf ("NumberOfEntries=%d\n", num_entries); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + if (success == FALSE) + { + gnome_vfs_close (handle); + return FALSE; + } + + for (i = 1; i <= num_entries_total; i++) { + GtkTreeIter iter; + char *url, *title, *relative; + gboolean custom_title; + + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, i - 1) == FALSE) + continue; + + func (model, &iter, &url, &title, &custom_title, user_data); + + if (totem_pl_parser_scheme_is_ignored (parser, url) != FALSE) + { + g_free (url); + g_free (title); + continue; + } + + relative = totem_pl_parser_relative (url, output); + buf = g_strdup_printf ("File%d=%s\n", i, + relative ? relative : url); + g_free (relative); + g_free (url); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + if (success == FALSE) + { + gnome_vfs_close (handle); + g_free (title); + return FALSE; + } + + if (custom_title == FALSE) { + g_free (title); + continue; + } + + buf = g_strdup_printf ("Title%d=%s\n", i, title); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + g_free (title); + if (success == FALSE) + { + gnome_vfs_close (handle); + return FALSE; + } + } + + gnome_vfs_close (handle); + return TRUE; +} + +TotemPlParserResult +totem_pl_parser_add_pls_with_contents (TotemPlParser *parser, const char *url, + const char *base, const char *contents) +{ + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + char **lines; + int i, num_entries; + char *split_char, *playlist_title; + gboolean dos_mode = FALSE; + gboolean fallback; + + /* figure out whether we're a unix pls or dos pls */ + if (strstr(contents,"\x0d") == NULL) { + split_char = "\n"; + } else { + split_char = "\x0d\n"; + dos_mode = TRUE; + } + lines = g_strsplit (contents, split_char, 0); + + /* [playlist] */ + i = 0; + playlist_title = NULL; + + /* Ignore empty lines */ + while (totem_pl_parser_line_is_empty (lines[i]) != FALSE) + i++; + + if (lines[i] == NULL + || g_ascii_strncasecmp (lines[i], "[playlist]", + (gsize)strlen ("[playlist]")) != 0) { + goto bail; + } + + playlist_title = totem_pl_parser_read_ini_line_string (lines, + "X-GNOME-Title", dos_mode); + + if (playlist_title != NULL) + totem_pl_parser_playlist_start (parser, playlist_title); + + /* numberofentries=? */ + num_entries = totem_pl_parser_read_ini_line_int (lines, "numberofentries"); + + if (num_entries == -1) { + num_entries = 0; + + for (i = 0; lines[i] != NULL; i++) { + if (totem_pl_parser_line_is_empty (lines[i])) + continue; + + if (g_ascii_strncasecmp (g_strchug (lines[i]), "file", (gsize)strlen ("file")) == 0) + num_entries++; + } + + if (num_entries == 0) + goto bail; + } + + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + for (i = 1; i <= num_entries; i++) { + char *file, *title, *genre; + char *file_key, *title_key, *genre_key; + + file_key = g_strdup_printf ("file%d", i); + title_key = g_strdup_printf ("title%d", i); + /* Genre is our own little extension */ + genre_key = g_strdup_printf ("genre%d", i); + + file = totem_pl_parser_read_ini_line_string (lines, (const char*)file_key, dos_mode); + title = totem_pl_parser_read_ini_line_string (lines, (const char*)title_key, dos_mode); + genre = totem_pl_parser_read_ini_line_string (lines, (const char*)genre_key, dos_mode); + + g_free (file_key); + g_free (title_key); + g_free (genre_key); + + if (file == NULL) + { + g_free (file); + g_free (title); + g_free (genre); + continue; + } + + fallback = parser->priv->fallback; + if (parser->priv->recurse) + parser->priv->fallback = FALSE; + + if (strstr (file, "://") != NULL || file[0] == G_DIR_SEPARATOR) { + if (totem_pl_parser_parse_internal (parser, file, NULL) != TOTEM_PL_PARSER_RESULT_SUCCESS) { + totem_pl_parser_add_url (parser, + "url", file, + "title", title, + "genre", genre, + "base", base, NULL); + } + } else { + char *base; + + /* Try with a base */ + base = totem_pl_parser_base_url (url); + + if (totem_pl_parser_parse_internal (parser, file, base) != TOTEM_PL_PARSER_RESULT_SUCCESS) { + char *escaped, *uri; + + escaped = gnome_vfs_escape_path_string (file); + uri = g_strdup_printf ("%s/%s", base, escaped); + g_free (escaped); + totem_pl_parser_add_url (parser, + "url", uri, + "title", title, + "genre", genre, + "base", base, NULL); + g_free (uri); + } + + g_free (base); + } + + parser->priv->fallback = fallback; + g_free (file); + g_free (title); + g_free (genre); + } + + if (playlist_title != NULL) + totem_pl_parser_playlist_end (parser, playlist_title); + +bail: + g_free (playlist_title); + g_strfreev (lines); + + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_pls (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + char *contents; + int size; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + if (size == 0) { + g_free (contents); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + retval = totem_pl_parser_add_pls_with_contents (parser, url, base, contents); + g_free (contents); + + return retval; +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + diff --git a/trunk/src/plparse/totem-pl-parser-pls.h b/trunk/src/plparse/totem-pl-parser-pls.h new file mode 100644 index 000000000..d2f97977d --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-pls.h @@ -0,0 +1,52 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_PLS_H +#define TOTEM_PL_PARSER_PLS_H + +G_BEGIN_DECLS + +#ifdef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser-mini.h" +#endif /* TOTEM_PL_PARSER_MINI */ + +#ifndef TOTEM_PL_PARSER_MINI +gboolean totem_pl_parser_write_pls (TotemPlParser *parser, + GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, + const char *title, + gpointer user_data, + GError **error); +TotemPlParserResult totem_pl_parser_add_pls_with_contents (TotemPlParser *parser, + const char *url, + const char *base, + const char *contents); +TotemPlParserResult totem_pl_parser_add_pls (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_PLS_H */ diff --git a/trunk/src/plparse/totem-pl-parser-private.h b/trunk/src/plparse/totem-pl-parser-private.h new file mode 100644 index 000000000..677bbdf1f --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-private.h @@ -0,0 +1,95 @@ +/* + 2002, 2003, 2004, 2005, 2006 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_PRIVATE_H +#define TOTEM_PL_PARSER_PRIVATE_H + +#include <glib.h> + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <glib-object.h> +#else +#include "totem-pl-parser-mini.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#define MIME_READ_CHUNK_SIZE 1024 +#define DEBUG(x) { if (parser->priv->debug) x; } + +struct TotemPlParserPrivate +{ + GList *ignore_schemes; + GList *ignore_mimetypes; +#ifndef TOTEM_PL_PARSER_MINI + GParamSpecPool *pspec_pool; +#endif + guint recurse_level; + guint fallback : 1; + guint recurse : 1; + guint debug : 1; + guint force : 1; + guint disable_unsafe : 1; +}; + +char *totem_pl_parser_read_ini_line_string (char **lines, const char *key, + gboolean dos_mode); +int totem_pl_parser_read_ini_line_int (char **lines, const char *key); +char *totem_pl_parser_read_ini_line_string_with_sep (char **lines, const char *key, + gboolean dos_mode, const char *sep); +char *totem_pl_resolve_url (const char *base, const char *url); +char *totem_pl_parser_base_url (const char *url); + +#ifndef TOTEM_PL_PARSER_MINI +void totem_pl_parser_playlist_start (TotemPlParser *parser, + const char *playlist_title); +void totem_pl_parser_playlist_end (TotemPlParser *parser, + const char *playlist_title); +int totem_pl_parser_num_entries (TotemPlParser *parser, + GtkTreeModel *model, + TotemPlParserIterFunc func, + gpointer user_data); +gboolean totem_pl_parser_scheme_is_ignored (TotemPlParser *parser, + const char *url); +gboolean totem_pl_parser_line_is_empty (const char *line); +gboolean totem_pl_parser_write_string (GnomeVFSHandle *handle, + const char *buf, + GError **error); +char * totem_pl_parser_relative (const char *url, + const char *output); +xmlDocPtr totem_pl_parser_parse_xml_file (const char *url); +TotemPlParserResult totem_pl_parser_parse_internal (TotemPlParser *parser, + const char *url, + const char *base); +void totem_pl_parser_add_one_url (TotemPlParser *parser, + const char *url, + const char *title); +void totem_pl_parser_add_url (TotemPlParser *parser, + const char *first_property_name, + ...); +gboolean totem_pl_parser_ignore (TotemPlParser *parser, const char *url); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_PRIVATE_H */ diff --git a/trunk/src/plparse/totem-pl-parser-qt.c b/trunk/src/plparse/totem-pl-parser-qt.c new file mode 100644 index 000000000..13db8a81d --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-qt.c @@ -0,0 +1,174 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <glib.h> + +#ifndef TOTEM_PL_PARSER_MINI +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-qt.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI + +static TotemPlParserResult +totem_pl_parser_add_quicktime_rtsptextrtsp (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data) +{ + char *contents = NULL; + const char *split_char; + int size; + char **lines; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + if (strstr(contents,"\x0d") == NULL) + split_char = "\n"; + else + split_char = "\x0d\n"; + + lines = g_strsplit (contents, split_char, 0); + g_free (contents); + + totem_pl_parser_add_one_url (parser, lines[0] + strlen ("RTSPtext"), NULL); + g_strfreev (lines); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +static TotemPlParserResult +totem_pl_parser_add_quicktime_metalink (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + xmlDocPtr doc; + xmlNodePtr node; + xmlChar *src; + + if (g_str_has_prefix (data, "RTSPtextRTSP://") != FALSE + || g_str_has_prefix (data, "rtsptextrtsp://") != FALSE + || g_str_has_prefix (data, "RTSPtextrtsp://") != FALSE) { + return totem_pl_parser_add_quicktime_rtsptextrtsp (parser, url, base, data); + } + + doc = totem_pl_parser_parse_xml_file (url); + + /* If the document has no root, or no name */ + if(!doc || !doc->children + || !doc->children->name + || g_ascii_strcasecmp ((char *)doc->children->name, + "quicktime") != 0) { + if (doc != NULL) + xmlFreeDoc (doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + if (strstr ((char *) doc->children->content, "type=\"application/x-quicktime-media-link\"") == NULL) { + xmlFreeDoc (doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + node = doc->children->next; + if (!node || !node->name + || g_ascii_strcasecmp ((char *) node->name, + "embed") != 0) { + xmlFreeDoc (doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + src = xmlGetProp (node, (const xmlChar *)"src"); + if (!src) { + xmlFreeDoc (doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + totem_pl_parser_add_one_url (parser, (char *) src, NULL); + + xmlFree (src); + xmlFreeDoc (doc); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +TotemPlParserResult +totem_pl_parser_add_quicktime (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + if (data == NULL || totem_pl_parser_is_quicktime (data, strlen (data)) == FALSE) { + totem_pl_parser_add_one_url (parser, url, NULL); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + return totem_pl_parser_add_quicktime_metalink (parser, url, base, data); +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + +gboolean +totem_pl_parser_is_quicktime (const char *data, gsize len) +{ + char *buffer; + + if (len == 0) + return FALSE; + if (len > MIME_READ_CHUNK_SIZE) + len = MIME_READ_CHUNK_SIZE; + + /* Check for RTSPtextRTSP Quicktime references */ + if (len <= strlen ("RTSPtextRTSP://")) + return FALSE; + if (g_str_has_prefix (data, "RTSPtextRTSP://") != FALSE + || g_str_has_prefix (data, "rtsptextrtsp://") != FALSE + || g_str_has_prefix (data, "RTSPtextrtsp://") != FALSE) { + return TRUE; + } + + /* FIXME would be nicer to have an strnstr */ + buffer = g_memdup (data, len); + if (buffer == NULL) { + g_warning ("Couldn't dup data in totem_pl_parser_is_quicktime"); + return FALSE; + } + buffer[len - 1] = '\0'; + if (strstr (buffer, "<?quicktime") != NULL) { + g_free (buffer); + return TRUE; + } + g_free (buffer); + return FALSE; +} + + diff --git a/trunk/src/plparse/totem-pl-parser-qt.h b/trunk/src/plparse/totem-pl-parser-qt.h new file mode 100644 index 000000000..886beed1b --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-qt.h @@ -0,0 +1,45 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_QT_H +#define TOTEM_PL_PARSER_QT_H + +G_BEGIN_DECLS + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#else +#include "totem-pl-parser-mini.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +gboolean totem_pl_parser_is_quicktime (const char *data, gsize len); + +#ifndef TOTEM_PL_PARSER_MINI +TotemPlParserResult totem_pl_parser_add_quicktime (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_QT_H */ diff --git a/trunk/src/plparse/totem-pl-parser-smil.c b/trunk/src/plparse/totem-pl-parser-smil.c new file mode 100644 index 000000000..6369c3aef --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-smil.c @@ -0,0 +1,186 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + + +#ifndef TOTEM_PL_PARSER_MINI +#include <glib.h> +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-smil.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI +static gboolean +parse_smil_video_entry (TotemPlParser *parser, char *base, + char *url, char *title) +{ + char *fullpath; + + fullpath = totem_pl_resolve_url (base, url); + totem_pl_parser_add_one_url (parser, fullpath, title); + + g_free (fullpath); + + return TRUE; +} + +static gboolean +parse_smil_entry (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent, xmlChar *parent_title) +{ + xmlNodePtr node; + xmlChar *title, *url; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR; + + title = NULL; + url = NULL; + + for (node = parent->children; node != NULL; node = node->next) + { + if (node->name == NULL) + continue; + + /* ENTRY should only have one ref and one title nodes */ + if (g_ascii_strcasecmp ((char *)node->name, "video") == 0 || g_ascii_strcasecmp ((char *)node->name, "audio") == 0) { + url = xmlGetProp (node, (const xmlChar *)"src"); + title = xmlGetProp (node, (const xmlChar *)"title"); + + if (url != NULL) { + if (parse_smil_video_entry (parser, + base, (char *)url, + title ? (char *)title + : (char *)parent_title) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + if (title) + xmlFree (title); + if (url) + xmlFree (url); + } else { + if (parse_smil_entry (parser, + base, doc, node, parent_title) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + } + + return retval; +} + +static xmlChar * +parse_smil_head (TotemPlParser *parser, xmlDocPtr doc, xmlNodePtr parent) +{ + xmlNodePtr node; + xmlChar *title = NULL; + + for (node = parent->children; node != NULL; node = node->next) { + if (g_ascii_strcasecmp ((char *)node->name, "meta") == 0) { + xmlChar *prop; + prop = xmlGetProp (node, (const xmlChar *)"name"); + if (prop != NULL && g_ascii_strcasecmp ((char *)prop, "title") == 0) { + title = xmlGetProp (node, (const xmlChar *)"content"); + if (title != NULL) { + xmlFree (prop); + break; + } + } + xmlFree (prop); + } + } + + return title; +} + +static gboolean +parse_smil_entries (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent) +{ + xmlNodePtr node; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR; + xmlChar *title = NULL; + + for (node = parent->children; node != NULL; node = node->next) { + if (node->name == NULL) + continue; + + if (g_ascii_strcasecmp ((char *)node->name, "body") == 0) { + if (parse_smil_entry (parser, base, + doc, node, title) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } else if (title == NULL) { + if (g_ascii_strcasecmp ((char *)node->name, "head") == 0) + title = parse_smil_head (parser, doc, node); + } + } + + if (title != NULL) + xmlFree (title); + + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_smil (TotemPlParser *parser, const char *url, + const char *_base, gpointer data) +{ + xmlDocPtr doc; + xmlNodePtr node; + char *base; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + + doc = totem_pl_parser_parse_xml_file (url); + + /* If the document has no root, or no name */ + if(!doc || !doc->children + || !doc->children->name + || g_ascii_strcasecmp ((char *)doc->children->name, + "smil") != 0) { + if (doc != NULL) + xmlFreeDoc (doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + base = totem_pl_parser_base_url (url); + + for (node = doc->children; node != NULL; node = node->next) + if (parse_smil_entries (parser, base, doc, node) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + g_free (base); + xmlFreeDoc (doc); + + return retval; +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + diff --git a/trunk/src/plparse/totem-pl-parser-smil.h b/trunk/src/plparse/totem-pl-parser-smil.h new file mode 100644 index 000000000..d3a5692a4 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-smil.h @@ -0,0 +1,43 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_SMIL_H +#define TOTEM_PL_PARSER_SMIL_H + +G_BEGIN_DECLS + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#else +#include "totem-pl-parser-mini.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#ifndef TOTEM_PL_PARSER_MINI +TotemPlParserResult totem_pl_parser_add_smil (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_SMIL_H */ diff --git a/trunk/src/plparse/totem-pl-parser-wm.c b/trunk/src/plparse/totem-pl-parser-wm.c new file mode 100644 index 000000000..62240251a --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-wm.c @@ -0,0 +1,346 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <glib.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> + +#ifndef TOTEM_PL_PARSER_MINI +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnomevfs/gnome-vfs-utils.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#include "totem-disc.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-wm.h" +#include "totem-pl-parser-lines.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI + +static TotemPlParserResult +totem_pl_parser_add_asf_reference_parser (TotemPlParser *parser, + const char *url, const char *base, + gpointer data) +{ + char *contents, **lines, *ref, *split_char; + int size; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + if (strstr(contents,"\x0d") == NULL) { + split_char = "\n"; + } else { + split_char = "\x0d\n"; + } + + lines = g_strsplit (contents, split_char, 0); + g_free (contents); + + /* Try to get Ref1 first */ + ref = totem_pl_parser_read_ini_line_string (lines, "Ref1", FALSE); + if (ref == NULL) { + g_strfreev (lines); + return totem_pl_parser_add_asx (parser, url, base, data); + } + + /* change http to mmsh, thanks Microsoft */ + if (g_str_has_prefix (ref, "http") != FALSE) + memcpy(ref, "mmsh", 4); + + totem_pl_parser_add_one_url (parser, ref, NULL); + g_free (ref); + + /* Don't try to get Ref2, as it's only ever + * supposed to be a fallback */ + + g_strfreev (lines); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +static TotemPlParserResult +totem_pl_parser_add_asf_parser (TotemPlParser *parser, + const char *url, const char *base, + gpointer data) +{ + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + char *contents, *ref; + int size; + + if (g_str_has_prefix (data, "[Address]") != FALSE) { + g_warning ("Implement NSC parsing: http://bugzilla.gnome.org/show_bug.cgi?id=350595"); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + if (g_str_has_prefix (data, "ASF ") == FALSE) { + return totem_pl_parser_add_asf_reference_parser (parser, url, base, data); + } + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return TOTEM_PL_PARSER_RESULT_ERROR; + + if (size <= 4) { + g_free (contents); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + /* Skip 'ASF ' */ + ref = contents + 4; + if (g_str_has_prefix (ref, "http") != FALSE) { + memcpy(ref, "mmsh", 4); + totem_pl_parser_add_one_url (parser, ref, NULL); + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + g_free (contents); + return retval; +} + +static gboolean +parse_asx_entry (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent, const char *pl_title) +{ + xmlNodePtr node; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + xmlChar *title, *url; + char *fullpath = NULL; + + title = NULL; + url = NULL; + + for (node = parent->children; node != NULL; node = node->next) { + if (node->name == NULL) + continue; + + /* ENTRY can only have one title node but multiple REFs */ + if (g_ascii_strcasecmp ((char *)node->name, "ref") == 0 + || g_ascii_strcasecmp ((char *)node->name, "entryref") == 0) { + xmlChar *tmp; + + tmp = xmlGetProp (node, (const xmlChar *)"href"); + if (tmp == NULL) + tmp = xmlGetProp (node, (const xmlChar *)"HREF"); + if (tmp == NULL) + continue; + /* FIXME, should we prefer mms streams, or non-mms? + * See bug #352559 */ + if (url == NULL) + url = tmp; + else + xmlFree (tmp); + + continue; + } + + if (g_ascii_strcasecmp ((char *)node->name, "title") == 0) + title = xmlNodeListGetString(doc, node->children, 1); + } + + if (url == NULL) { + if (title) + xmlFree (title); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + fullpath = totem_pl_resolve_url (base, (char *)url); + + xmlFree (url); + + /* .asx files can contain references to other .asx files */ + retval = totem_pl_parser_parse_internal (parser, fullpath, NULL); + if (retval != TOTEM_PL_PARSER_RESULT_SUCCESS) { + totem_pl_parser_add_one_url (parser, fullpath, + (char *)title ? (char *)title : pl_title); + } + + g_free (fullpath); + if (title) + xmlFree (title); + + return retval; +} + +static gboolean +parse_asx_entries (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent) +{ + xmlChar *title = NULL; + xmlNodePtr node; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR; + xmlChar *newbase = NULL; + + for (node = parent->children; node != NULL; node = node->next) { + if (node->name == NULL) + continue; + + if (g_ascii_strcasecmp ((char *)node->name, "title") == 0) { + title = xmlNodeListGetString(doc, node->children, 1); + } + if (g_ascii_strcasecmp ((char *)node->name, "base") == 0) { + newbase = xmlGetProp (node, (const xmlChar *)"href"); + if (newbase == NULL) + newbase = xmlGetProp (node, (const xmlChar *)"HREF"); + if (newbase != NULL) + base = (char *)newbase; + } + + if (g_ascii_strcasecmp ((char *)node->name, "entry") == 0) { + /* Whee found an entry here, find the REF and TITLE */ + if (parse_asx_entry (parser, base, doc, node, (char *)title) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + if (g_ascii_strcasecmp ((char *)node->name, "entryref") == 0) { + /* Found an entryref, give the parent instead of the + * children to the parser */ + if (parse_asx_entry (parser, base, doc, parent, (char *)title) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + if (g_ascii_strcasecmp ((char *)node->name, "repeat") == 0) { + /* Repeat at the top-level */ + if (parse_asx_entries (parser, base, doc, node) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + } + + if (newbase) + xmlFree (newbase); + + if (title) + xmlFree (title); + + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_asx (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + xmlDocPtr doc; + xmlNodePtr node; + char *_base; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + + if (data != NULL && totem_pl_parser_is_uri_list (data, strlen (data)) != FALSE) { + return totem_pl_parser_add_ram (parser, url, data); + } + + doc = totem_pl_parser_parse_xml_file (url); + + /* If the document has no root, or no name */ + if(!doc || !doc->children || !doc->children->name) { + if (doc != NULL) + xmlFreeDoc(doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + if (base == NULL) { + _base = totem_pl_parser_base_url (url); + } else { + _base = g_strdup (base); + } + + for (node = doc->children; node != NULL; node = node->next) + if (parse_asx_entries (parser, _base, doc, node) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + g_free (_base); + xmlFreeDoc(doc); + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_asf (TotemPlParser *parser, const char *url, + const char *base, gpointer data) +{ + if (data == NULL) { + totem_pl_parser_add_one_url (parser, url, NULL); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + if (totem_pl_parser_is_asf (data, strlen (data)) == FALSE) { + totem_pl_parser_add_one_url (parser, url, NULL); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + return totem_pl_parser_add_asf_parser (parser, url, base, data); +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + +gboolean +totem_pl_parser_is_asx (const char *data, gsize len) +{ + char *buffer; + + if (len == 0) + return FALSE; + + if (g_ascii_strncasecmp (data, "<ASX", strlen ("<ASX")) == 0) + return TRUE; + + if (len > MIME_READ_CHUNK_SIZE) + len = MIME_READ_CHUNK_SIZE; + + /* FIXME would be nicer to have an strnstr */ + buffer = g_memdup (data, len); + if (buffer == NULL) { + g_warning ("Couldn't dup data in totem_pl_parser_is_asx"); + return FALSE; + } + buffer[len - 1] = '\0'; + if (strstr (buffer, "<ASX") != NULL + || strstr (buffer, "<asx") != NULL) { + g_free (buffer); + return TRUE; + } + g_free (buffer); + + return FALSE; +} + +gboolean +totem_pl_parser_is_asf (const char *data, gsize len) +{ + if (len == 0) + return FALSE; + + if (g_str_has_prefix (data, "[Reference]") != FALSE + || g_str_has_prefix (data, "ASF ") != FALSE + || g_str_has_prefix (data, "[Address]") != FALSE) { + return TRUE; + } + + return totem_pl_parser_is_asx (data, len); +} + diff --git a/trunk/src/plparse/totem-pl-parser-wm.h b/trunk/src/plparse/totem-pl-parser-wm.h new file mode 100644 index 000000000..bca4e810f --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-wm.h @@ -0,0 +1,50 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_WM_H +#define TOTEM_PL_PARSER_WM_H + +G_BEGIN_DECLS + +#ifndef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser.h" +#else +#include "totem-pl-parser-mini.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +gboolean totem_pl_parser_is_asf (const char *data, gsize len); +gboolean totem_pl_parser_is_asx (const char *data, gsize len); + +#ifndef TOTEM_PL_PARSER_MINI +TotemPlParserResult totem_pl_parser_add_asf (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +TotemPlParserResult totem_pl_parser_add_asx (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_WM_H */ diff --git a/trunk/src/plparse/totem-pl-parser-xspf.c b/trunk/src/plparse/totem-pl-parser-xspf.c new file mode 100644 index 000000000..fc58ac161 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-xspf.c @@ -0,0 +1,261 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + + +#ifndef TOTEM_PL_PARSER_MINI +#include <string.h> +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-xspf.h" +#include "totem-pl-parser-private.h" + +#ifndef TOTEM_PL_PARSER_MINI +gboolean +totem_pl_parser_write_xspf (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, const char *title, + gpointer user_data, GError **error) +{ + GnomeVFSHandle *handle; + GnomeVFSResult res; + int num_entries_total, num_entries, i; + char *buf; + gboolean success; + + num_entries = totem_pl_parser_num_entries (parser, model, func, user_data); + num_entries_total = gtk_tree_model_iter_n_children (model, NULL); + + res = gnome_vfs_open (&handle, output, GNOME_VFS_OPEN_WRITE); + if (res == GNOME_VFS_ERROR_NOT_FOUND) { + res = gnome_vfs_create (&handle, output, + GNOME_VFS_OPEN_WRITE, FALSE, + GNOME_VFS_PERM_USER_WRITE + | GNOME_VFS_PERM_USER_READ + | GNOME_VFS_PERM_GROUP_READ); + } + + if (res != GNOME_VFS_OK) { + g_set_error(error, + TOTEM_PL_PARSER_ERROR, + TOTEM_PL_PARSER_ERROR_VFS_OPEN, + _("Couldn't open file '%s': %s"), + output, gnome_vfs_result_to_string (res)); + return FALSE; + } + + buf = g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n" + " <trackList>\n"); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + if (success == FALSE) + { + gnome_vfs_close (handle); + return FALSE; + } + + for (i = 1; i <= num_entries_total; i++) { + GtkTreeIter iter; + char *url, *url_escaped, *relative, *title; + gboolean custom_title; + + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, i - 1) == FALSE) + continue; + + func (model, &iter, &url, &title, &custom_title, user_data); + + if (totem_pl_parser_scheme_is_ignored (parser, url) != FALSE) + { + g_free (url); + g_free (title); + continue; + } + + relative = totem_pl_parser_relative (url, output); + url_escaped = g_markup_escape_text (relative ? relative : url, -1); + buf = g_strdup_printf (" <track>\n" + " <location>%s</location>\n", url_escaped); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (url); + g_free (url_escaped); + g_free (relative); + g_free (buf); + if (success == FALSE) + { + gnome_vfs_close (handle); + g_free (title); + return FALSE; + } + + if (custom_title == TRUE) + buf = g_strdup_printf (" <title>%s</title>\n" + " </track>\n", title); + else + buf = g_strdup_printf (" </track>\n"); + + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + g_free (title); + if (success == FALSE) + { + gnome_vfs_close (handle); + return FALSE; + } + } + + buf = g_strdup_printf (" </trackList>\n" + "</playlist>"); + success = totem_pl_parser_write_string (handle, buf, error); + g_free (buf); + gnome_vfs_close (handle); + + return success; +} + +static gboolean +parse_xspf_track (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent) +{ + xmlNodePtr node; + xmlChar *title, *url; + gchar *fullpath; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR; + + title = NULL; + url = NULL; + + for (node = parent->children; node != NULL; node = node->next) + { + if (node->name == NULL) + continue; + + if (g_ascii_strcasecmp ((char *)node->name, "location") == 0) + url = xmlNodeListGetString (doc, node->xmlChildrenNode, 1); + + if (g_ascii_strcasecmp ((char *)node->name, "title") == 0) + title = xmlNodeListGetString (doc, node->xmlChildrenNode, 1); + } + + if (url == NULL) { + if (title) + xmlFree (title); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + fullpath = totem_pl_resolve_url (base, (char *)url); + totem_pl_parser_add_one_url (parser, fullpath, (char *)title); + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + if (title) + xmlFree (title); + if (url) + xmlFree (url); + g_free (fullpath); + + return retval; +} + +static gboolean +parse_xspf_trackList (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent) +{ + xmlNodePtr node; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR; + + for (node = parent->children; node != NULL; node = node->next) + { + if (node->name == NULL) + continue; + + if (g_ascii_strcasecmp ((char *)node->name, "track") == 0) + if (parse_xspf_track (parser, base, doc, node) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + return retval; +} + +static gboolean +parse_xspf_entries (TotemPlParser *parser, char *base, xmlDocPtr doc, + xmlNodePtr parent) +{ + xmlNodePtr node; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_ERROR; + + for (node = parent->children; node != NULL; node = node->next) { + if (node->name == NULL) + continue; + + if (g_ascii_strcasecmp ((char *)node->name, "trackList") == 0) + if (parse_xspf_trackList (parser, base, doc, node) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + return retval; +} + +TotemPlParserResult +totem_pl_parser_add_xspf (TotemPlParser *parser, const char *url, + const char *_base, gpointer data) +{ + xmlDocPtr doc; + xmlNodePtr node; + char *base; + TotemPlParserResult retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + + doc = totem_pl_parser_parse_xml_file (url); + + /* If the document has no root, or no name */ + if(!doc || !doc->children + || !doc->children->name + || g_ascii_strcasecmp ((char *)doc->children->name, + "playlist") != 0) { + if (doc != NULL) + xmlFreeDoc(doc); + return TOTEM_PL_PARSER_RESULT_ERROR; + } + + base = totem_pl_parser_base_url (url); + + for (node = doc->children; node != NULL; node = node->next) + if (parse_xspf_entries (parser, base, doc, node) != FALSE) + retval = TOTEM_PL_PARSER_RESULT_SUCCESS; + + g_free (base); + xmlFreeDoc(doc); + return retval; +} +#endif /* !TOTEM_PL_PARSER_MINI */ + diff --git a/trunk/src/plparse/totem-pl-parser-xspf.h b/trunk/src/plparse/totem-pl-parser-xspf.h new file mode 100644 index 000000000..e79380867 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser-xspf.h @@ -0,0 +1,48 @@ +/* + 2002, 2003, 2004, 2005, 2006, 2007 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_XSPF_H +#define TOTEM_PL_PARSER_XSPF_H + +G_BEGIN_DECLS + +#ifdef TOTEM_PL_PARSER_MINI +#include "totem-pl-parser-mini.h" +#endif /* TOTEM_PL_PARSER_MINI */ + +#ifndef TOTEM_PL_PARSER_MINI +gboolean totem_pl_parser_write_xspf (TotemPlParser *parser, + GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, + const char *title, + gpointer user_data, + GError **error); +TotemPlParserResult totem_pl_parser_add_xspf (TotemPlParser *parser, + const char *url, + const char *base, + gpointer data); +#endif /* !TOTEM_PL_PARSER_MINI */ + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_XSPF_H */ diff --git a/trunk/src/plparse/totem-pl-parser.c b/trunk/src/plparse/totem-pl-parser.c new file mode 100644 index 000000000..a0100bf1d --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser.c @@ -0,0 +1,1206 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006 Bastien Nocera + Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <glib.h> +#include <glib/gi18n-lib.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> + +#ifndef TOTEM_PL_PARSER_MINI +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <gobject/gvaluecollector.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnomevfs/gnome-vfs-utils.h> +#include "totem-pl-parser.h" +#include "totemplparser-marshal.h" +#include "totem-disc.h" +#endif /* !TOTEM_PL_PARSER_MINI */ + +#include "totem-pl-parser-mini.h" +#include "totem-pl-parser-wm.h" +#include "totem-pl-parser-qt.h" +#include "totem-pl-parser-pls.h" +#include "totem-pl-parser-xspf.h" +#include "totem-pl-parser-media.h" +#include "totem-pl-parser-smil.h" +#include "totem-pl-parser-lines.h" +#include "totem-pl-parser-misc.h" +#include "totem-pl-parser-private.h" + +#define READ_CHUNK_SIZE 8192 +#define RECURSE_LEVEL_MAX 4 +#define DIR_MIME_TYPE "x-directory/normal" +#define BLOCK_DEVICE_TYPE "x-special/device-block" +#define EMPTY_FILE_TYPE "application/x-zerosize" +#define TEXT_URI_TYPE "text/uri-list" +#define AUDIO_MPEG_TYPE "audio/mpeg" + +typedef gboolean (*PlaylistIdenCallback) (const char *data, gsize len); + +#ifndef TOTEM_PL_PARSER_MINI +typedef TotemPlParserResult (*PlaylistCallback) (TotemPlParser *parser, const char *url, const char *base, gpointer data); +#endif + +typedef struct { + char *mimetype; +#ifndef TOTEM_PL_PARSER_MINI + PlaylistCallback func; +#endif + PlaylistIdenCallback iden; +#ifndef TOTEM_PL_PARSER_MINI + guint unsafe : 1; +#endif +} PlaylistTypes; + +#ifndef TOTEM_PL_PARSER_MINI + +static void totem_pl_parser_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void totem_pl_parser_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +enum { + PROP_NONE, + PROP_RECURSE, + PROP_DEBUG, + PROP_FORCE, + PROP_DISABLE_UNSAFE +}; + +/* Signals */ +enum { + ENTRY, + PLAYLIST_START, + PLAYLIST_END, + LAST_SIGNAL +}; + +static int totem_pl_parser_table_signals[LAST_SIGNAL]; +static gboolean i18n_done = FALSE; + +static void totem_pl_parser_class_init (TotemPlParserClass *class); +static void totem_pl_parser_init (TotemPlParser *parser); +static void totem_pl_parser_finalize (GObject *object); + +G_DEFINE_TYPE(TotemPlParser, totem_pl_parser, G_TYPE_OBJECT) + +static void +totem_pl_parser_class_init (TotemPlParserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = totem_pl_parser_finalize; + object_class->set_property = totem_pl_parser_set_property; + object_class->get_property = totem_pl_parser_get_property; + + /* properties */ + g_object_class_install_property (object_class, + PROP_RECURSE, + g_param_spec_boolean ("recurse", + "recurse", + "Whether or not to process URLs further", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_DEBUG, + g_param_spec_boolean ("debug", + "debug", + "Whether or not to enable debugging output", + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_FORCE, + g_param_spec_boolean ("force", + "force", + "Whether or not to force parsing the file if the playlist looks unsupported", + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_DISABLE_UNSAFE, + g_param_spec_boolean ("disable-unsafe", + "disable-unsafe", + "Whether or not to disable parsing of unsafe locations", + FALSE, + G_PARAM_READWRITE)); + + /* Signals */ + totem_pl_parser_table_signals[ENTRY] = + g_signal_new ("entry", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlParserClass, entry), + NULL, NULL, + totemplparser_marshal_VOID__STRING_STRING_STRING, + G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + totem_pl_parser_table_signals[PLAYLIST_START] = + g_signal_new ("playlist-start", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlParserClass, playlist_start), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + totem_pl_parser_table_signals[PLAYLIST_END] = + g_signal_new ("playlist-end", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlParserClass, playlist_end), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +totem_pl_parser_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TotemPlParser *parser = TOTEM_PL_PARSER (object); + + switch (prop_id) + { + case PROP_RECURSE: + parser->priv->recurse = g_value_get_boolean (value) != FALSE; + break; + case PROP_DEBUG: + parser->priv->debug = g_value_get_boolean (value) != FALSE; + break; + case PROP_FORCE: + parser->priv->force = g_value_get_boolean (value) != FALSE; + break; + case PROP_DISABLE_UNSAFE: + parser->priv->disable_unsafe = g_value_get_boolean (value) != FALSE; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +totem_pl_parser_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TotemPlParser *parser = TOTEM_PL_PARSER (object); + + switch (prop_id) + { + case PROP_RECURSE: + g_value_set_boolean (value, parser->priv->recurse); + break; + case PROP_DEBUG: + g_value_set_boolean (value, parser->priv->debug); + break; + case PROP_FORCE: + g_value_set_boolean (value, parser->priv->force); + break; + case PROP_DISABLE_UNSAFE: + g_value_set_boolean (value, parser->priv->disable_unsafe); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GQuark +totem_pl_parser_error_quark (void) +{ + static GQuark quark; + if (!quark) + quark = g_quark_from_static_string ("totem_pl_parser_error"); + + return quark; +} + +static void +totem_pl_parser_init_i18n (void) +{ + if (i18n_done == FALSE) { + /* set up translation catalog */ + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + i18n_done = TRUE; + } +} + +TotemPlParser * +totem_pl_parser_new (void) +{ + totem_pl_parser_init_i18n (); + return TOTEM_PL_PARSER (g_object_new (TOTEM_TYPE_PL_PARSER, NULL)); +} + +void +totem_pl_parser_playlist_start (TotemPlParser *parser, const char *playlist_title) +{ + g_signal_emit (G_OBJECT (parser), + totem_pl_parser_table_signals[PLAYLIST_START], + 0, playlist_title); +} + +void +totem_pl_parser_playlist_end (TotemPlParser *parser, const char *playlist_title) +{ + g_signal_emit (G_OBJECT (parser), + totem_pl_parser_table_signals[PLAYLIST_END], + 0, playlist_title); +} + +static char * +my_gnome_vfs_get_mime_type_with_data (const char *uri, gpointer *data, TotemPlParser *parser) +{ + GnomeVFSResult result; + GnomeVFSHandle *handle; + char *buffer; + const char *mimetype; + GnomeVFSFileSize total_bytes_read; + GnomeVFSFileSize bytes_read; + + *data = NULL; + + /* Stat for a block device, we're screwed as far as speed + * is concerned now */ + if (g_str_has_prefix (uri, "file://") != FALSE) { + struct stat buf; + if (stat (uri + strlen ("file://"), &buf) == 0) { + if (S_ISBLK (buf.st_mode)) + return g_strdup (BLOCK_DEVICE_TYPE); + } + } + + /* Open the file. */ + result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ); + if (result != GNOME_VFS_OK) { + if (result == GNOME_VFS_ERROR_IS_DIRECTORY) + return g_strdup (DIR_MIME_TYPE); + DEBUG(g_print ("URL '%s' couldn't be opened in _get_mime_type_with_data: '%s'\n", uri, gnome_vfs_result_to_string (result))); + return NULL; + } + DEBUG(g_print ("URL '%s' was opened successfully in _get_mime_type_with_data:\n", uri)); + + /* Read the whole thing, up to MIME_READ_CHUNK_SIZE */ + buffer = NULL; + total_bytes_read = 0; + bytes_read = 0; + do { + buffer = g_realloc (buffer, total_bytes_read + + MIME_READ_CHUNK_SIZE); + result = gnome_vfs_read (handle, + buffer + total_bytes_read, + MIME_READ_CHUNK_SIZE, + &bytes_read); + if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) { + g_free (buffer); + gnome_vfs_close (handle); + return NULL; + } + + /* Check for overflow. */ + if (total_bytes_read + bytes_read < total_bytes_read) { + g_free (buffer); + gnome_vfs_close (handle); + return NULL; + } + + total_bytes_read += bytes_read; + } while (result == GNOME_VFS_OK + && total_bytes_read < MIME_READ_CHUNK_SIZE); + + /* Close the file but don't overwrite the possible error */ + if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) + gnome_vfs_close (handle); + else + result = gnome_vfs_close (handle); + + if (result != GNOME_VFS_OK) { + DEBUG(g_print ("URL '%s' couldn't be read or closed in _get_mime_type_with_data: '%s'\n", uri, gnome_vfs_result_to_string (result))); + g_free (buffer); + return NULL; + } + + /* Empty file */ + if (total_bytes_read == 0) { + DEBUG(g_print ("URL '%s' is empty in _get_mime_type_with_data\n", uri)); + return g_strdup (EMPTY_FILE_TYPE); + } + + /* Return the file null-terminated. */ + buffer = g_realloc (buffer, total_bytes_read + 1); + buffer[total_bytes_read] = '\0'; + *data = buffer; + + mimetype = gnome_vfs_get_mime_type_for_data (*data, total_bytes_read); + + if (mimetype != NULL && strcmp (mimetype, "text/plain") == 0) { + if (totem_pl_parser_is_uri_list (*data, total_bytes_read) != FALSE) + return g_strdup (TEXT_URI_TYPE); + } + + return g_strdup (mimetype); +} + +xmlDocPtr +totem_pl_parser_parse_xml_file (const char *url) +{ + xmlDocPtr doc; + char *contents; + int size; + + if (gnome_vfs_read_entire_file (url, &size, &contents) != GNOME_VFS_OK) + return NULL; + + /* Try to remove HTML style comments */ + { + char *needle; + + while ((needle = strstr (contents, "<!--")) != NULL) { + while (strncmp (needle, "-->", 3) != 0) { + *needle = ' '; + needle++; + if (*needle == '\0') + break; + } + } + } + + doc = xmlParseMemory (contents, size); + if (doc == NULL) + doc = xmlRecoverMemory (contents, size); + g_free (contents); + + return doc; +} + +char * +totem_pl_parser_base_url (const char *url) +{ + /* Yay, let's reconstruct the base by hand */ + GnomeVFSURI *uri, *parent; + char *base; + + uri = gnome_vfs_uri_new (url); + if (uri == NULL) + return NULL; + + parent = gnome_vfs_uri_get_parent (uri); + if (!parent) { + parent = uri; + } + base = gnome_vfs_uri_to_string (parent, 0); + + gnome_vfs_uri_unref (uri); + if (parent != uri) { + gnome_vfs_uri_unref (parent); + } + + return base; +} + +gboolean +totem_pl_parser_line_is_empty (const char *line) +{ + guint i; + + if (line == NULL) + return TRUE; + + for (i = 0; line[i] != '\0'; i++) { + if (line[i] != '\t' && line[i] != ' ') + return FALSE; + } + return TRUE; +} + +gboolean +totem_pl_parser_write_string (GnomeVFSHandle *handle, const char *buf, GError **error) +{ + GnomeVFSResult res; + GnomeVFSFileSize written; + guint len; + + len = strlen (buf); + res = gnome_vfs_write (handle, buf, len, &written); + if (res != GNOME_VFS_OK || written < len) { + g_set_error (error, + TOTEM_PL_PARSER_ERROR, + TOTEM_PL_PARSER_ERROR_VFS_WRITE, + _("Couldn't write parser: %s"), + gnome_vfs_result_to_string (res)); + gnome_vfs_close (handle); + return FALSE; + } + + return TRUE; +} + +int +totem_pl_parser_num_entries (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, gpointer user_data) +{ + int num_entries, i, ignored; + + num_entries = gtk_tree_model_iter_n_children (model, NULL); + ignored = 0; + + for (i = 1; i <= num_entries; i++) + { + GtkTreeIter iter; + char *url, *title; + gboolean custom_title; + + if (gtk_tree_model_iter_nth_child (model, &iter, NULL, i -1) == FALSE) + return i - ignored; + + func (model, &iter, &url, &title, &custom_title, user_data); + if (totem_pl_parser_scheme_is_ignored (parser, url) != FALSE) + ignored++; + + g_free (url); + g_free (title); + } + + return num_entries - ignored; +} + +char * +totem_pl_parser_relative (const char *url, const char *output) +{ + char *url_base, *output_base; + char *base, *needle; + + base = NULL; + url_base = totem_pl_parser_base_url (url); + if (url_base == NULL) + return NULL; + + output_base = totem_pl_parser_base_url (output); + + needle = strstr (url_base, output_base); + if (needle != NULL) + { + GnomeVFSURI *uri; + char *newurl; + + uri = gnome_vfs_uri_new (url); + newurl = gnome_vfs_uri_to_string (uri, 0); + if (newurl[strlen (output_base)] == '/') { + base = g_strdup (newurl + strlen (output_base) + 1); + } else { + base = g_strdup (newurl + strlen (output_base)); + } + gnome_vfs_uri_unref (uri); + g_free (newurl); + + /* And finally unescape the string */ + newurl = gnome_vfs_unescape_string (base, NULL); + g_free (base); + base = newurl; + } + + g_free (url_base); + g_free (output_base); + + return base; +} + +#ifndef TOTEM_PL_PARSER_MINI +gboolean +totem_pl_parser_write_with_title (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, const char *title, + TotemPlParserType type, + gpointer user_data, GError **error) +{ + switch (type) + { + case TOTEM_PL_PARSER_PLS: + return totem_pl_parser_write_pls (parser, model, func, + output, title, user_data, error); + case TOTEM_PL_PARSER_M3U: + case TOTEM_PL_PARSER_M3U_DOS: + return totem_pl_parser_write_m3u (parser, model, func, + output, (type == TOTEM_PL_PARSER_M3U_DOS), + user_data, error); + case TOTEM_PL_PARSER_XSPF: + return totem_pl_parser_write_xspf (parser, model, func, + output, title, user_data, error); + default: + g_assert_not_reached (); + } + + return FALSE; +} + +gboolean +totem_pl_parser_write (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, TotemPlParserType type, + gpointer user_data, + GError **error) +{ + return totem_pl_parser_write_with_title (parser, model, func, output, + NULL, type, user_data, error); +} + +#endif /* TOTEM_PL_PARSER_MINI */ + +int +totem_pl_parser_read_ini_line_int (char **lines, const char *key) +{ + int retval = -1; + int i; + + if (lines == NULL || key == NULL) + return -1; + + for (i = 0; (lines[i] != NULL && retval == -1); i++) { + char *line = lines[i]; + + while (*line == '\t' || *line == ' ') + line++; + + if (g_ascii_strncasecmp (line, key, strlen (key)) == 0) { + char **bits; + + bits = g_strsplit (line, "=", 2); + if (bits[0] == NULL || bits [1] == NULL) { + g_strfreev (bits); + return -1; + } + + retval = (gint) g_strtod (bits[1], NULL); + g_strfreev (bits); + } + } + + return retval; +} + +char* +totem_pl_parser_read_ini_line_string_with_sep (char **lines, const char *key, + gboolean dos_mode, const char *sep) +{ + char *retval = NULL; + int i; + + if (lines == NULL || key == NULL) + return NULL; + + for (i = 0; (lines[i] != NULL && retval == NULL); i++) { + char *line = lines[i]; + + while (*line == '\t' || *line == ' ') + line++; + + if (g_ascii_strncasecmp (line, key, strlen (key)) == 0) { + char **bits; + ssize_t len; + + bits = g_strsplit (line, sep, 2); + if (bits[0] == NULL || bits [1] == NULL) { + g_strfreev (bits); + return NULL; + } + + retval = g_strdup (bits[1]); + len = strlen (retval); + if (dos_mode && len >= 2 && retval[len-2] == '\r') { + retval[len-2] = '\n'; + retval[len-1] = '\0'; + } + + g_strfreev (bits); + } + } + + return retval; +} + +char* +totem_pl_parser_read_ini_line_string (char **lines, const char *key, gboolean dos_mode) +{ + return totem_pl_parser_read_ini_line_string_with_sep (lines, key, dos_mode, "="); +} + +static void +totem_pl_parser_init (TotemPlParser *parser) +{ + GParamSpec *pspec; + parser->priv = g_new0 (TotemPlParserPrivate, 1); + + parser->priv->pspec_pool = g_param_spec_pool_new (FALSE); + pspec = g_param_spec_string ("url", "url", + "URL to be added", NULL, + G_PARAM_READABLE & G_PARAM_WRITABLE); + g_param_spec_pool_insert (parser->priv->pspec_pool, pspec, TOTEM_TYPE_PL_PARSER); + pspec = g_param_spec_string ("title", "title", + "Title of the item to be added", NULL, + G_PARAM_READABLE & G_PARAM_WRITABLE); + g_param_spec_pool_insert (parser->priv->pspec_pool, pspec, TOTEM_TYPE_PL_PARSER); + pspec = g_param_spec_string ("genre", "genre", + "Genre of the item to be added", NULL, + G_PARAM_READABLE & G_PARAM_WRITABLE); + g_param_spec_pool_insert (parser->priv->pspec_pool, pspec, TOTEM_TYPE_PL_PARSER); + pspec = g_param_spec_string ("base", "base", + "Base URL of the item to be added", NULL, + G_PARAM_READABLE & G_PARAM_WRITABLE); + g_param_spec_pool_insert (parser->priv->pspec_pool, pspec, TOTEM_TYPE_PL_PARSER); +} + +static void +totem_pl_parser_finalize (GObject *object) +{ + TotemPlParser *parser = TOTEM_PL_PARSER (object); + + g_return_if_fail (object != NULL); + g_return_if_fail (parser->priv != NULL); + + g_list_foreach (parser->priv->ignore_schemes, (GFunc) g_free, NULL); + g_list_free (parser->priv->ignore_schemes); + + g_list_foreach (parser->priv->ignore_mimetypes, (GFunc) g_free, NULL); + g_list_free (parser->priv->ignore_mimetypes); + + g_free (parser->priv); + parser->priv = NULL; + + G_OBJECT_CLASS (totem_pl_parser_parent_class)->finalize (object); +} + +//FIXME remove ? +static gboolean +totem_pl_parser_check_utf8 (const char *title) +{ + return title ? g_utf8_validate (title, -1, NULL) : FALSE; +} + +static void +totem_pl_parser_add_url_valist (TotemPlParser *parser, + const gchar *first_property_name, + va_list var_args) +{ + const char *name; + char *title, *url, *genre, *base; + + title = url = genre = base = NULL; + + g_object_ref (G_OBJECT (parser)); + + name = first_property_name; + + while (name) { + GValue value = { 0, }; + GParamSpec *pspec; + char *error = NULL; + + pspec = g_param_spec_pool_lookup (parser->priv->pspec_pool, + name, + G_OBJECT_TYPE (parser), + FALSE); + + if (!pspec) { + g_warning ("Unknown property '%s'", name); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + G_VALUE_COLLECT (&value, var_args, 0, &error); + if (error != NULL) { + g_warning ("Error getting the value for property '%s'", name); + break; + } + + if (strcmp (name, "url") == 0) { + url = g_value_dup_string (&value); + } else if (strcmp (name, "title") == 0) { + title = g_value_dup_string (&value); + } else if (strcmp (name, "genre") == 0) { + genre = g_value_dup_string (&value); + } else if (strcmp (name, "base") == 0) { + base = g_value_dup_string (&value); + } + + g_value_unset (&value); + name = va_arg (var_args, char*); + } + + g_assert (url != NULL); + + if (parser->priv->disable_unsafe != FALSE) { + //FIXME fix this! 396710 + } + + g_signal_emit (G_OBJECT (parser), totem_pl_parser_table_signals[ENTRY], + 0, url, title, genre); + + g_free (url); + g_free (title); + g_free (genre); + g_free (base); + + g_object_unref (G_OBJECT (parser)); +} + +void +totem_pl_parser_add_url (TotemPlParser *parser, + const char *first_property_name, + ...) +{ + va_list var_args; + va_start (var_args, first_property_name); + totem_pl_parser_add_url_valist (parser, first_property_name, var_args); + va_end (var_args); +} + +void +totem_pl_parser_add_one_url (TotemPlParser *parser, const char *url, const char *title) +{ + totem_pl_parser_add_url (parser, "url", url, "title", title, NULL); +} + +char * +totem_pl_resolve_url (const char *base, const char *url) +{ + GnomeVFSURI *base_uri, *new; + char *resolved; + + /* If the URI isn't relative, just leave */ + if (strstr (url, "://") != NULL) + return g_strdup (url); + + base_uri = gnome_vfs_uri_new (base); + g_return_val_if_fail (base_uri != NULL, g_strdup (url)); + new = gnome_vfs_uri_resolve_relative (base_uri, url); + g_return_val_if_fail (new != NULL, g_strdup (url)); + gnome_vfs_uri_unref (base_uri); + resolved = gnome_vfs_uri_to_string (new, GNOME_VFS_URI_HIDE_NONE); + gnome_vfs_uri_unref (new); + + return resolved; +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + +#ifndef TOTEM_PL_PARSER_MINI +#define PLAYLIST_TYPE(mime,cb,identcb,unsafe) { mime, cb, identcb, unsafe } +#define PLAYLIST_TYPE2(mime,cb,identcb) { mime, cb, identcb } +#define PLAYLIST_TYPE3(mime) { mime, NULL, NULL, FALSE } +#else +#define PLAYLIST_TYPE(mime,cb,identcb,unsafe) { mime } +#define PLAYLIST_TYPE2(mime,cb,identcb) { mime, identcb } +#define PLAYLIST_TYPE3(mime) { mime } +#endif + +/* These ones need a special treatment, mostly parser formats */ +static PlaylistTypes special_types[] = { + PLAYLIST_TYPE ("audio/x-mpegurl", totem_pl_parser_add_m3u, NULL, FALSE), + PLAYLIST_TYPE ("audio/playlist", totem_pl_parser_add_m3u, NULL, FALSE), + PLAYLIST_TYPE ("audio/x-scpls", totem_pl_parser_add_pls, NULL, FALSE), + PLAYLIST_TYPE ("application/x-smil", totem_pl_parser_add_smil, NULL, FALSE), + PLAYLIST_TYPE ("application/smil", totem_pl_parser_add_smil, NULL, FALSE), + PLAYLIST_TYPE ("video/x-ms-wvx", totem_pl_parser_add_asx, NULL, FALSE), + PLAYLIST_TYPE ("audio/x-ms-wax", totem_pl_parser_add_asx, NULL, FALSE), + PLAYLIST_TYPE ("application/xspf+xml", totem_pl_parser_add_xspf, NULL, FALSE), + PLAYLIST_TYPE ("text/uri-list", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list, FALSE), + PLAYLIST_TYPE ("text/x-google-video-pointer", totem_pl_parser_add_gvp, NULL, FALSE), + PLAYLIST_TYPE ("text/google-video-pointer", totem_pl_parser_add_gvp, NULL, FALSE), +#ifndef TOTEM_PL_PARSER_MINI + PLAYLIST_TYPE ("application/x-desktop", totem_pl_parser_add_desktop, NULL, TRUE), + PLAYLIST_TYPE ("application/x-gnome-app-info", totem_pl_parser_add_desktop, NULL, TRUE), + PLAYLIST_TYPE ("application/x-cd-image", totem_pl_parser_add_iso, NULL, TRUE), + PLAYLIST_TYPE ("application/x-extension-img", totem_pl_parser_add_iso, NULL, TRUE), + PLAYLIST_TYPE ("application/x-cue", totem_pl_parser_add_cue, NULL, TRUE), + PLAYLIST_TYPE (DIR_MIME_TYPE, totem_pl_parser_add_directory, NULL, TRUE), + PLAYLIST_TYPE (BLOCK_DEVICE_TYPE, totem_pl_parser_add_block, NULL, TRUE), +#endif +}; + +/* These ones are "dual" types, might be a video, might be a parser */ +static PlaylistTypes dual_types[] = { + PLAYLIST_TYPE2 ("audio/x-real-audio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("audio/x-pn-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("application/vnd.rn-realmedia", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("audio/x-pn-realaudio-plugin", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("audio/vnd.rn-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("audio/x-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("text/plain", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list), + PLAYLIST_TYPE2 ("audio/x-ms-asx", totem_pl_parser_add_asx, totem_pl_parser_is_asx), + PLAYLIST_TYPE2 ("video/x-ms-asf", totem_pl_parser_add_asf, totem_pl_parser_is_asf), + PLAYLIST_TYPE2 ("video/x-ms-wmv", totem_pl_parser_add_asf, totem_pl_parser_is_asf), + PLAYLIST_TYPE2 ("video/quicktime", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime), + PLAYLIST_TYPE2 ("application/x-quicktime-media-link", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime), + PLAYLIST_TYPE2 ("application/x-quicktimeplayer", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime), +}; + +#ifndef TOTEM_PL_PARSER_MINI + +static PlaylistTypes ignore_types[] = { + PLAYLIST_TYPE3 ("image/*"), + PLAYLIST_TYPE3 ("text/plain"), + PLAYLIST_TYPE3 ("application/x-rar"), + PLAYLIST_TYPE3 ("application/zip"), + PLAYLIST_TYPE3 ("application/x-trash"), +}; + +gboolean +totem_pl_parser_scheme_is_ignored (TotemPlParser *parser, const char *url) +{ + GList *l; + + if (parser->priv->ignore_schemes == NULL) + return FALSE; + + for (l = parser->priv->ignore_schemes; l != NULL; l = l->next) + { + const char *scheme = l->data; + if (g_str_has_prefix (url, scheme) != FALSE) + return TRUE; + } + + return FALSE; +} + +static gboolean +totem_pl_parser_mimetype_is_ignored (TotemPlParser *parser, + const char *mimetype) +{ + GList *l; + + if (parser->priv->ignore_mimetypes == NULL) + return FALSE; + + for (l = parser->priv->ignore_mimetypes; l != NULL; l = l->next) + { + const char *item = l->data; + if (strcmp (mimetype, item) == 0) + return TRUE; + } + + return FALSE; + +} + +gboolean +totem_pl_parser_ignore (TotemPlParser *parser, const char *url) +{ + const char *mimetype; + guint i; + + if (totem_pl_parser_scheme_is_ignored (parser, url) != FALSE) + return TRUE; + + mimetype = gnome_vfs_get_file_mime_type (url, NULL, TRUE); + if (mimetype == NULL || strcmp (mimetype, GNOME_VFS_MIME_TYPE_UNKNOWN) == 0) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (special_types); i++) + if (strcmp (special_types[i].mimetype, mimetype) == 0) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (dual_types); i++) + if (strcmp (dual_types[i].mimetype, mimetype) == 0) + return FALSE; + + return TRUE; +} + +static gboolean +totem_pl_parser_ignore_from_mimetype (TotemPlParser *parser, const char *mimetype) +{ + char *super; + guint i; + + super = gnome_vfs_get_supertype_from_mime_type (mimetype); + for (i = 0; i < G_N_ELEMENTS (ignore_types) && super != NULL; i++) { + if (gnome_vfs_mime_type_is_supertype (ignore_types[i].mimetype) != FALSE) { + if (strcmp (super, ignore_types[i].mimetype) == 0) { + g_free (super); + return TRUE; + } + } else { + GnomeVFSMimeEquivalence eq; + + eq = gnome_vfs_mime_type_get_equivalence (mimetype, ignore_types[i].mimetype); + if (eq == GNOME_VFS_MIME_PARENT || eq == GNOME_VFS_MIME_IDENTICAL) { + g_free (super); + return TRUE; + } + } + } + g_free (super); + + return FALSE; +} + +TotemPlParserResult +totem_pl_parser_parse_internal (TotemPlParser *parser, const char *url, + const char *base) +{ + char *mimetype; + guint i; + gpointer data = NULL; + TotemPlParserResult ret = TOTEM_PL_PARSER_RESULT_ERROR; + gboolean found = FALSE; + + if (parser->priv->recurse_level > RECURSE_LEVEL_MAX) + return TOTEM_PL_PARSER_RESULT_ERROR; + + /* Shouldn't gnome-vfs have a list of schemes it supports? */ + if (g_str_has_prefix (url, "mms") != FALSE + || g_str_has_prefix (url, "rtsp") != FALSE + || g_str_has_prefix (url, "icy") != FALSE) { + DEBUG(g_print ("URL '%s' is MMS, RTSP or ICY, ignoring\n", url)); + return TOTEM_PL_PARSER_RESULT_UNHANDLED; + } + + if (!parser->priv->recurse && parser->priv->recurse_level > 0) { + return TOTEM_PL_PARSER_RESULT_UNHANDLED; + } + + /* In force mode we want to get the data */ + if (parser->priv->force != FALSE) { + mimetype = my_gnome_vfs_get_mime_type_with_data (url, &data, parser); + } else { + mimetype = g_strdup (gnome_vfs_get_mime_type_for_name (url)); + } + + DEBUG(g_print ("_get_mime_type_for_name for '%s' returned '%s'\n", url, mimetype)); + if (mimetype == NULL || strcmp (GNOME_VFS_MIME_TYPE_UNKNOWN, mimetype) == 0) { + mimetype = my_gnome_vfs_get_mime_type_with_data (url, &data, parser); + DEBUG(g_print ("_get_mime_type_with_data for '%s' returned '%s'\n", url, mimetype ? mimetype : "NULL")); + } + + if (mimetype == NULL) { + g_free (data); + return TOTEM_PL_PARSER_RESULT_UNHANDLED; + } + + if (strcmp (mimetype, EMPTY_FILE_TYPE) == 0) { + g_free (data); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + /* If we're at the top-level of the parsing, try to get more + * data from the playlist parser */ + if (strcmp (mimetype, AUDIO_MPEG_TYPE) == 0 && parser->priv->recurse_level == 0 && data == NULL) { + char *tmp; + tmp = my_gnome_vfs_get_mime_type_with_data (url, &data, parser); + if (tmp != NULL) { + g_free (mimetype); + mimetype = tmp; + } + DEBUG(g_print ("_get_mime_type_with_data for '%s' returned '%s' (was %s)\n", url, mimetype, AUDIO_MPEG_TYPE)); + } + + if (totem_pl_parser_mimetype_is_ignored (parser, mimetype) != FALSE) { + g_free (mimetype); + g_free (data); + return TOTEM_PL_PARSER_RESULT_IGNORED; + } + + if (parser->priv->recurse || parser->priv->recurse_level == 0) { + parser->priv->recurse_level++; + + for (i = 0; i < G_N_ELEMENTS(special_types); i++) { + if (strcmp (special_types[i].mimetype, mimetype) == 0) { + DEBUG(g_print ("URL '%s' is special type '%s'\n", url, mimetype)); + if (parser->priv->disable_unsafe != FALSE && special_types[i].unsafe != FALSE) { + g_free (mimetype); + g_free (data); + return TOTEM_PL_PARSER_RESULT_IGNORED; + } + ret = (* special_types[i].func) (parser, url, base, data); + found = TRUE; + break; + } + } + + for (i = 0; i < G_N_ELEMENTS(dual_types) && found == FALSE; i++) { + if (strcmp (dual_types[i].mimetype, mimetype) == 0) { + DEBUG(g_print ("URL '%s' is dual type '%s'\n", url, mimetype)); + if (data == NULL) { + g_free (mimetype); + mimetype = my_gnome_vfs_get_mime_type_with_data (url, &data, parser); + } + ret = (* dual_types[i].func) (parser, url, base, data); + found = TRUE; + break; + } + } + + g_free (data); + + parser->priv->recurse_level--; + } + + if (ret == TOTEM_PL_PARSER_RESULT_SUCCESS) { + g_free (mimetype); + return ret; + } + + if (totem_pl_parser_ignore_from_mimetype (parser, mimetype)) { + g_free (mimetype); + return TOTEM_PL_PARSER_RESULT_IGNORED; + } + g_free (mimetype); + + if (ret != TOTEM_PL_PARSER_RESULT_SUCCESS && parser->priv->fallback) { + totem_pl_parser_add_one_url (parser, url, NULL); + return TOTEM_PL_PARSER_RESULT_SUCCESS; + } + + return ret; +} + +TotemPlParserResult +totem_pl_parser_parse_with_base (TotemPlParser *parser, const char *url, + const char *base, gboolean fallback) +{ + g_return_val_if_fail (TOTEM_IS_PL_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED); + g_return_val_if_fail (url != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED); + + if (totem_pl_parser_scheme_is_ignored (parser, url) != FALSE) + return TOTEM_PL_PARSER_RESULT_UNHANDLED; + + g_return_val_if_fail (strstr (url, "://") != NULL, + TOTEM_PL_PARSER_RESULT_IGNORED); + + parser->priv->recurse_level = 0; + parser->priv->fallback = fallback != FALSE; + return totem_pl_parser_parse_internal (parser, url, base); +} + +TotemPlParserResult +totem_pl_parser_parse (TotemPlParser *parser, const char *url, + gboolean fallback) +{ + return totem_pl_parser_parse_with_base (parser, url, NULL, fallback); +} + +void +totem_pl_parser_add_ignored_scheme (TotemPlParser *parser, + const char *scheme) +{ + g_return_if_fail (TOTEM_IS_PL_PARSER (parser)); + + parser->priv->ignore_schemes = g_list_prepend + (parser->priv->ignore_schemes, g_strdup (scheme)); +} + +void +totem_pl_parser_add_ignored_mimetype (TotemPlParser *parser, + const char *mimetype) +{ + g_return_if_fail (TOTEM_IS_PL_PARSER (parser)); + + parser->priv->ignore_mimetypes = g_list_prepend + (parser->priv->ignore_mimetypes, g_strdup (mimetype)); +} + +#endif /* !TOTEM_PL_PARSER_MINI */ + +#define D(x) if (debug) x + +gboolean +totem_pl_parser_can_parse_from_data (const char *data, + gsize len, + gboolean debug) +{ + const char *mimetype; + guint i; + + g_return_val_if_fail (data != NULL, FALSE); + + /* Bad cast! */ + mimetype = gnome_vfs_get_mime_type_for_data ((gpointer) data, (int) len); + + if (mimetype == NULL || strcmp (GNOME_VFS_MIME_TYPE_UNKNOWN, mimetype) == 0) { + D(g_message ("totem_pl_parser_can_parse_from_data couldn't get mimetype")); + return FALSE; + } + + for (i = 0; i < G_N_ELEMENTS(special_types); i++) { + if (strcmp (special_types[i].mimetype, mimetype) == 0) { + D(g_message ("Is special type '%s'", mimetype)); + return TRUE; + } + } + + for (i = 0; i < G_N_ELEMENTS(dual_types); i++) { + if (strcmp (dual_types[i].mimetype, mimetype) == 0) { + D(g_message ("Should be dual type '%s', making sure now", mimetype)); + if (dual_types[i].iden != NULL) { + gboolean retval = (* dual_types[i].iden) (data, len); + D(g_message ("%s dual type '%s'", + retval ? "Is" : "Is not", mimetype)); + return retval; + } + return FALSE; + } + } + + return FALSE; +} + +gboolean +totem_pl_parser_can_parse_from_filename (const char *filename, gboolean debug) +{ + GMappedFile *map; + GError *err = NULL; + gboolean retval; + + g_return_val_if_fail (filename != NULL, FALSE); + + map = g_mapped_file_new (filename, FALSE, &err); + if (map == NULL) { + D(g_message ("couldn't mmap %s: %s", filename, err->message)); + g_error_free (err); + return FALSE; + } + + retval = totem_pl_parser_can_parse_from_data + (g_mapped_file_get_contents (map), + g_mapped_file_get_length (map), debug); + + g_mapped_file_free (map); + + return retval; +} + diff --git a/trunk/src/plparse/totem-pl-parser.h b/trunk/src/plparse/totem-pl-parser.h new file mode 100644 index 000000000..41431a9e1 --- /dev/null +++ b/trunk/src/plparse/totem-pl-parser.h @@ -0,0 +1,123 @@ +/* + 2002, 2003, 2004, 2005, 2006 Bastien Nocera + Copyright (C) 2003 Colin Walters <walters@verbum.org> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PL_PARSER_H +#define TOTEM_PL_PARSER_H + +#include <glib.h> + +#include <gtk/gtktreemodel.h> +#include "totem-pl-parser-features.h" +#include "totem-pl-parser-builtins.h" + +G_BEGIN_DECLS + +#define TOTEM_TYPE_PL_PARSER (totem_pl_parser_get_type ()) +#define TOTEM_PL_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_PL_PARSER, TotemPlParser)) +#define TOTEM_PL_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_PL_PARSER, TotemPlParserClass)) +#define TOTEM_IS_PL_PARSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_PL_PARSER)) +#define TOTEM_IS_PL_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_PL_PARSER)) + +typedef enum +{ + TOTEM_PL_PARSER_RESULT_UNHANDLED, + TOTEM_PL_PARSER_RESULT_ERROR, + TOTEM_PL_PARSER_RESULT_SUCCESS, + TOTEM_PL_PARSER_RESULT_IGNORED +} TotemPlParserResult; + +typedef struct TotemPlParser TotemPlParser; +typedef struct TotemPlParserClass TotemPlParserClass; +typedef struct TotemPlParserPrivate TotemPlParserPrivate; + +struct TotemPlParser { + GObject parent; + TotemPlParserPrivate *priv; +}; + +struct TotemPlParserClass { + GObjectClass parent_class; + + /* signals */ + void (*entry) (TotemPlParser *parser, const char *uri, + const char *title, const char *genre); + void (*playlist_start) (TotemPlParser *parser, const char *title); + void (*playlist_end) (TotemPlParser *parser, const char *title); +}; + +typedef enum +{ + TOTEM_PL_PARSER_PLS, + TOTEM_PL_PARSER_M3U, + TOTEM_PL_PARSER_M3U_DOS, + TOTEM_PL_PARSER_XSPF, +} TotemPlParserType; + +typedef enum +{ + TOTEM_PL_PARSER_ERROR_VFS_OPEN, + TOTEM_PL_PARSER_ERROR_VFS_WRITE, +} TotemPlParserError; + +#define TOTEM_PL_PARSER_ERROR (totem_pl_parser_error_quark ()) + +GQuark totem_pl_parser_error_quark (void); + +typedef void (*TotemPlParserIterFunc) (GtkTreeModel *model, GtkTreeIter *iter, + char **uri, char **title, + gboolean *custom_title, + gpointer user_data); + +GType totem_pl_parser_get_type (void); + +gboolean totem_pl_parser_write (TotemPlParser *parser, GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, TotemPlParserType type, + gpointer user_data, + GError **error); + +gboolean totem_pl_parser_write_with_title (TotemPlParser *parser, + GtkTreeModel *model, + TotemPlParserIterFunc func, + const char *output, + const char *title, + TotemPlParserType type, + gpointer user_data, + GError **error); + +void totem_pl_parser_add_ignored_scheme (TotemPlParser *parser, + const char *scheme); +void totem_pl_parser_add_ignored_mimetype (TotemPlParser *parser, + const char *mimetype); + +TotemPlParserResult totem_pl_parser_parse (TotemPlParser *parser, + const char *url, gboolean fallback); +TotemPlParserResult totem_pl_parser_parse_with_base (TotemPlParser *parser, + const char *url, + const char *base, + gboolean fallback); + +TotemPlParser *totem_pl_parser_new (void); + +G_END_DECLS + +#endif /* TOTEM_PL_PARSER_H */ diff --git a/trunk/src/plparse/totemplparser-marshal.list b/trunk/src/plparse/totemplparser-marshal.list new file mode 100644 index 000000000..41e40276c --- /dev/null +++ b/trunk/src/plparse/totemplparser-marshal.list @@ -0,0 +1 @@ +VOID:STRING,STRING,STRING diff --git a/trunk/src/test-properties-page.c b/trunk/src/test-properties-page.c new file mode 100644 index 000000000..8e5d8aab9 --- /dev/null +++ b/trunk/src/test-properties-page.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005 Bastien Nocera <hadess@hadess.net> + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> +#include <string.h> +#include <glib/gi18n-lib.h> +#include "totem-properties-view.h" +#include "bacon-video-widget.h" + +static int i; +GtkWidget *window, *props, *label; + +static gboolean +main_loop_exit (gpointer data) +{ + g_print ("Finishing %d\n", i); + gtk_main_quit (); + return FALSE; +} + +static void +create_props (const char *url) +{ + label = gtk_label_new ("Audio/Video"); + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + props = totem_properties_view_new (url, label); + gtk_container_add (GTK_CONTAINER (window), props); + + gtk_widget_show_all (window); +} + +static void +destroy_props (void) +{ + gtk_widget_destroy (window); + gtk_widget_destroy (label); +} + +int main (int argc, char **argv) +{ + int times = 10; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_thread_init (NULL); + gtk_init (&argc, &argv); + + if (argc < 2) { + g_print ("Usage: %s [URI]\n", argv[0]); + return 1; + } + + bacon_video_widget_init_backend (NULL, NULL); +#if 0 + create_props (argv[1]); +#endif + + if (argc == 3) { + times = g_strtod (argv[2], NULL); + } + + for (i = 0; i < times; i++) { + g_print ("Setting %d\n", i); +#if 0 + totem_properties_view_set_location (TOTEM_PROPERTIES_VIEW (props), argv[1]); +#else + create_props (argv[1]); +#endif + g_timeout_add (4000, main_loop_exit, NULL); + gtk_main (); +#if 1 + destroy_props (); +#endif + } + +// gtk_main (); + + return 0; +} + diff --git a/trunk/src/test.html b/trunk/src/test.html new file mode 100644 index 000000000..d2bb9284a --- /dev/null +++ b/trunk/src/test.html @@ -0,0 +1,51 @@ +<html> +<head> +<title>Test</title> +</head> +<body> +<div align="middle"> +<h1>PIMP</h1> +<h3>PIMP Internet Media Player</h3> +<table cellspacing="12" cellpadding="0"> +<tr> + <td colspan="3"> +<embed src="file:///tmp/dmp.mov" + width="384" height="300" controller="false" enablejavascript="true" + bgcolor=#000000 autoplay="true" name="player"/> + </td> +</tr> +<tr> + <td align="middle"> + <img src="file:///usr/share/icons/Bluecurve/48x48/stock/media-play.png" + onClick="javascript:document.player.Play();"/> + </td> + <td align="middle"> + <img src="file:///usr/share/icons/Bluecurve/48x48/stock/media-pause.png" + onClick="javascript:document.player.Stop();"/> + </td> + <td align="middle"> + <img src="file:///usr/share/icons/Bluecurve/48x48/stock/media-stop.png" + onClick="javascript:document.player.Rewind();"/> + </td> +</td> +</table> +<script><!-- + Maybe add in future: + - kiosk mode + + Javascript controls: + Play() -> play + Rewind() -> stop + Stop() -> pause + GetTimeScale() -> get units per second (e.g. milliseconds) + SetTime(int time) / GetTime() -> query/seek (in timescale) + GetVolume() / SetVolume (int vol) -> set/get volume (?) + SetIsLooping(boolean loop) / GetIsLooping() -> get/set loop + GetMute() / SetMute(boolean mute) -> set/get mute + SetTarget(string url) / GetTarget() -> change HREF + SetURL(string url) / GetURL() -> change URL + GetDuration() -> get movie length (in timescale) +--></script> +</div> +</body> +</html> diff --git a/trunk/src/totem-gromit.c b/trunk/src/totem-gromit.c new file mode 100644 index 000000000..a6190f87d --- /dev/null +++ b/trunk/src/totem-gromit.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> + +#ifndef HAVE_GTK_ONLY + +#include <glib.h> + +#include <sys/types.h> +#include <signal.h> +#include <fcntl.h> +#include <unistd.h> + +#include "totem-gromit.h" + +#define INTERVAL 10000 + +static const char *start_cmd[] = { NULL, "-a", "-k", "none", NULL }; +static const char *toggle_cmd[] = { NULL, "-t", NULL }; +static const char *clear_cmd[] = { NULL, "-c", NULL }; +static const char *visibility_cmd[] = { NULL, "-v", NULL }; +/* no quit command, we just kill the process */ + +static char *path = NULL; +static int id = -1; +static GPid pid = -1; + +#define DEFAULT_CONFIG \ +"#Default gromit configuration for Totem's telestrator mode \n\ +\"red Pen\" = PEN (size=5 color=\"red\"); \n\ +\"blue Pen\" = \"red Pen\" (color=\"blue\"); \n\ +\"yellow Pen\" = \"red Pen\" (color=\"yellow\"); \n\ +\"green Marker\" = PEN (size=6 color=\"green\" arrowsize=1); \n\ + \n\ +\"Eraser\" = ERASER (size = 100); \n\ + \n\ +\"Core Pointer\" = \"red Pen\"; \n\ +\"Core Pointer\"[SHIFT] = \"blue Pen\"; \n\ +\"Core Pointer\"[CONTROL] = \"yellow Pen\"; \n\ +\"Core Pointer\"[2] = \"green Marker\"; \n\ +\"Core Pointer\"[Button3] = \"Eraser\"; \n\ +\n" + +static void +totem_gromit_ensure_config_file (void) +{ + char *path; + int fd; + + path = g_build_filename (g_get_home_dir (), ".gromitrc", NULL); + if (g_file_test (path, G_FILE_TEST_EXISTS) != FALSE) { + g_free (path); + return; + } + + g_message ("%s doesn't exist", path); + + fd = creat (path, 0755); + g_free (path); + if (fd < 0) { + return; + } + + write (fd, DEFAULT_CONFIG, sizeof (DEFAULT_CONFIG)); + close (fd); +} + +gboolean +totem_gromit_available (void) +{ + static int gromit_available = -1; + + if (gromit_available != -1) + return (gboolean) gromit_available; + + path = g_find_program_in_path ("gromit"); + gromit_available = (path != NULL); + if (path != NULL) { + start_cmd[0] = toggle_cmd[0] = clear_cmd[0] = + visibility_cmd[0] = path; + totem_gromit_ensure_config_file (); + } + + return gromit_available; +} + +static void +launch (const char **cmd) +{ + g_spawn_sync (NULL, (char **)cmd, NULL, 0, NULL, NULL, + NULL, NULL, NULL, NULL); +} + +static void +gromit_exit (void) +{ + /* Nothing to do */ + if (pid == -1) { + if (id != -1) { + g_source_remove (id); + id = -1; + } + return; + } + + kill ((pid_t) pid, SIGKILL); + pid = -1; +} + +static gboolean +gromit_timeout_cb (gpointer data) +{ + id = -1; + gromit_exit (); + return FALSE; +} + +#include <gtk/gtk.h> + +void +totem_gromit_toggle (void) +{ + if (totem_gromit_available () == FALSE) + return; + + /* Not started */ + if (pid == -1) { + if (g_spawn_async (NULL, + (char **)start_cmd, NULL, 0, NULL, NULL, + &pid, NULL) == FALSE) { + g_printerr ("Couldn't start gromit"); + return; + } + } else if (id == -1) { /* Started but disabled */ + g_source_remove (id); + id = -1; + launch (toggle_cmd); + } else { + /* Started and visible */ + g_source_remove (id); + id = -1; + launch (toggle_cmd); + } +} + +void +totem_gromit_clear (gboolean now) +{ + if (totem_gromit_available () == FALSE) + return; + + if (now != FALSE) { + gromit_exit (); + if (path != NULL) { + g_free (path); + path = NULL; + } + return; + } + + launch (visibility_cmd); + launch (clear_cmd); + id = g_timeout_add (INTERVAL, gromit_timeout_cb, NULL); +} + +#endif /* !HAVE_GTK_ONLY */ diff --git a/trunk/src/totem-gromit.h b/trunk/src/totem-gromit.h new file mode 100644 index 000000000..aa74adda2 --- /dev/null +++ b/trunk/src/totem-gromit.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <glib.h> + +#ifndef __TOTEM_GROMIT_H +#define __TOTEM_GROMIT_H + +G_BEGIN_DECLS + +gboolean totem_gromit_available (void); +void totem_gromit_toggle (void); +void totem_gromit_clear (gboolean now); + +G_END_DECLS + +#endif /* __TOTEM_GROMIT_H */ diff --git a/trunk/src/totem-interface.c b/trunk/src/totem-interface.c new file mode 100644 index 000000000..2711c332e --- /dev/null +++ b/trunk/src/totem-interface.c @@ -0,0 +1,256 @@ +/* totem-interface.c + * + * Copyright (C) 2005 Bastien Nocera + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Bastien Nocera <hadess@hadess.net> + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "totem-interface.h" + +static GtkWidget * +totem_interface_error_dialog (const char *title, const char *reason, + GtkWindow *parent) +{ + GtkWidget *error_dialog; + + if (reason == NULL) + g_warning ("totem_action_error called with reason == NULL"); + + error_dialog = + gtk_message_dialog_new (NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", title); + gtk_message_dialog_format_secondary_text + (GTK_MESSAGE_DIALOG (error_dialog), "%s", reason); + + totem_interface_set_transient_for (GTK_WINDOW (error_dialog), + GTK_WINDOW (parent)); + gtk_window_set_title (GTK_WINDOW (error_dialog), ""); /* as per HIG */ + gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5); + gtk_dialog_set_default_response (GTK_DIALOG (error_dialog), + GTK_RESPONSE_OK); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + + return error_dialog; +} + +void +totem_interface_error (const char *title, const char *reason, + GtkWindow *parent) +{ + GtkWidget *error_dialog; + + error_dialog = totem_interface_error_dialog (title, reason, parent); + + g_signal_connect (G_OBJECT (error_dialog), "destroy", G_CALLBACK + (gtk_widget_destroy), error_dialog); + g_signal_connect (G_OBJECT (error_dialog), "response", G_CALLBACK + (gtk_widget_destroy), error_dialog); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + + gtk_widget_show (error_dialog); +} + +void +totem_interface_error_blocking (const char *title, const char *reason, + GtkWindow *parent) +{ + GtkWidget *error_dialog; + + error_dialog = totem_interface_error_dialog (title, reason, parent); + + gtk_dialog_run (GTK_DIALOG (error_dialog)); + gtk_widget_destroy (error_dialog); +} + +GladeXML * +totem_interface_load_with_root (const char *name, const char *root_widget, + const char *display_name, gboolean fatal, GtkWindow *parent) +{ + GladeXML *glade; + char *filename; + + glade = NULL; + filename = totem_interface_get_full_path (name); + + if (filename != NULL) + glade = glade_xml_new (filename, root_widget, GETTEXT_PACKAGE); + g_free (filename); + + if (glade == NULL) + { + char *msg; + + msg = g_strdup_printf (_("Couldn't load the '%s' interface."), display_name); + if (fatal == FALSE) + totem_interface_error (msg, _("Make sure that Totem is properly installed."), parent); + else + totem_interface_error_blocking (msg, _("Make sure that Totem is properly installed."), parent); + + g_free (msg); + return NULL; + } + + return glade; +} + +GladeXML * +totem_interface_load (const char *name, const char *display_name, + gboolean fatal, GtkWindow *parent) +{ + return totem_interface_load_with_root (name, NULL, display_name, + fatal, parent); +} + +GdkPixbuf* +totem_interface_load_pixbuf (const char *name) +{ + GdkPixbuf *pix; + char *filename; + + filename = totem_interface_get_full_path (name); + if (filename == NULL) + return NULL; + pix = gdk_pixbuf_new_from_file (filename, NULL); + g_free (filename); + return pix; +} + +char * +totem_interface_get_full_path (const char *name) +{ + char *filename; + +#ifdef TOTEM_RUN_IN_SOURCE_TREE + /* Try the glade file in the source tree first */ + filename = g_build_filename ("..", "data", name, NULL); + if (g_file_test (filename, G_FILE_TEST_EXISTS) == FALSE) + { + g_free (filename); + /* Try the local file */ + filename = g_build_filename (DATADIR, + "totem", name, NULL); + + if (g_file_test (filename, G_FILE_TEST_EXISTS) == FALSE) + { + g_free (filename); + return NULL; + } + } +#else + filename = g_build_filename (DATADIR, + "totem", name, NULL); +#endif + + return filename; +} + +static GdkWindow * +totem_gtk_plug_get_toplevel (GtkPlug *plug) +{ + Window root, parent, *children; + guint nchildren; + GdkNativeWindow xid; + + g_return_val_if_fail (GTK_IS_PLUG (plug), NULL); + + xid = gtk_plug_get_id (plug); + + do + { + /* FIXME: multi-head */ + if (XQueryTree (GDK_DISPLAY (), xid, &root, + &parent, &children, &nchildren) == 0) + { + g_warning ("Couldn't find window manager window"); + return None; + } + + if (root == parent) { + GdkWindow *toplevel; + toplevel = gdk_window_foreign_new (xid); + return toplevel; + } + + xid = parent; + } + while (TRUE); +} + +void +totem_interface_set_transient_for (GtkWindow *window, GtkWindow *parent) +{ + if (GTK_IS_PLUG (parent)) { + GdkWindow *toplevel; + + gtk_widget_realize (GTK_WIDGET (window)); + toplevel = totem_gtk_plug_get_toplevel (GTK_PLUG (parent)); + if (toplevel != NULL) { + gdk_window_set_transient_for + (GTK_WIDGET (window)->window, toplevel); + g_object_unref (toplevel); + } + } else { + gtk_window_set_transient_for (GTK_WINDOW (window), + GTK_WINDOW (parent)); + } +} + +char * +totem_interface_get_license (void) +{ + const char const *license[] = { + N_("Totem 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."), + N_("Totem 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."), + N_("You should have received a copy of the GNU General Public License " + "along with Totem; if not, write to the Free Software Foundation, Inc., " + "59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"), + N_("Totem contains an exception to allow the use of proprietary " + "GStreamer plugins.") + }; + return g_strconcat (_(license[0]), "\n\n", + _(license[1]), "\n\n", + _(license[2]), "\n\n", + _(license[3]), "\n", + NULL); +} + diff --git a/trunk/src/totem-interface.h b/trunk/src/totem-interface.h new file mode 100644 index 000000000..55ca77c88 --- /dev/null +++ b/trunk/src/totem-interface.h @@ -0,0 +1,54 @@ +/* totem-interface.h + + Copyright (C) 2005 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_INTERFACE_H +#define TOTEM_INTERFACE_H + +#include <glade/glade.h> +#include <gtk/gtkwindow.h> + +G_BEGIN_DECLS + +GdkPixbuf *totem_interface_load_pixbuf (const char *name); +char *totem_interface_get_full_path (const char *name); +GladeXML *totem_interface_load (const char *name, + const char *display_name, + gboolean fatal, + GtkWindow *parent); +GladeXML *totem_interface_load_with_root (const char *name, + const char *root_widget, + const char *display_name, + gboolean fatal, + GtkWindow *parent); +void totem_interface_error (const char *title, + const char *reason, + GtkWindow *parent); +void totem_interface_error_blocking (const char *title, + const char *reason, + GtkWindow *parent); +void totem_interface_set_transient_for (GtkWindow *window, + GtkWindow *parent); +char * totem_interface_get_license (void); + +G_END_DECLS + +#endif /* TOTEM_INTERFACE_H */ diff --git a/trunk/src/totem-marshal.list b/trunk/src/totem-marshal.list new file mode 100644 index 000000000..72f993790 --- /dev/null +++ b/trunk/src/totem-marshal.list @@ -0,0 +1 @@ +VOID:STRING,STRING diff --git a/trunk/src/totem-menu.c b/trunk/src/totem-menu.c new file mode 100644 index 000000000..10ae4b5de --- /dev/null +++ b/trunk/src/totem-menu.c @@ -0,0 +1,1388 @@ +/* totem-menu.c + + Copyright (C) 2004-2005 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glade/glade.h> +#include <string.h> + +#include "totem-menu.h" +#include "totem.h" +#include "totem-interface.h" +#include "totem-private.h" +#include "totem-sidebar.h" +#include "bacon-video-widget.h" + +#include "debug.h" + +/* Helper function to escape underscores in labels + * before putting them in menu items */ +static char * +escape_label_for_menu (const char *name) +{ + char *new, **a; + + a = g_strsplit (name, "_", -1); + new = g_strjoinv ("__", a); + g_strfreev (a); + + return new; +} + +/* ISO-639 helpers */ +static GHashTable *lang_table; + +static void +totem_lang_table_free (void) +{ + g_hash_table_destroy (lang_table); + lang_table = NULL; +} + +static void +totem_lang_table_parse_start_tag (GMarkupParseContext *ctx, + const gchar *element_name, + const gchar **attr_names, + const gchar **attr_values, + gpointer data, + GError **error) +{ + const char *ccode_longB, *ccode_longT, *ccode, *lang_name; + + if (!g_str_equal (element_name, "iso_639_entry") + || attr_names == NULL + || attr_values == NULL) + return; + + ccode = NULL; + ccode_longB = NULL; + ccode_longT = NULL; + lang_name = NULL; + + while (*attr_names && *attr_values) + { + if (g_str_equal (*attr_names, "iso_639_1_code")) + { + /* skip if empty */ + if (**attr_values) + { + g_return_if_fail (strlen (*attr_values) == 2); + ccode = *attr_values; + } + } else if (g_str_equal (*attr_names, "iso_639_2B_code")) { + /* skip if empty */ + if (**attr_values) + { + g_return_if_fail (strlen (*attr_values) == 3 || strcmp (*attr_values, "qaa-qtz") == 0); + ccode_longB = *attr_values; + } + } else if (g_str_equal (*attr_names, "iso_639_2T_code")) { + /* skip if empty */ + if (**attr_values) + { + g_return_if_fail (strlen (*attr_values) == 3 || strcmp (*attr_values, "qaa-qtz") == 0); + ccode_longT = *attr_values; + } + } else if (g_str_equal (*attr_names, "name")) { + lang_name = *attr_values; + } + + ++attr_names; + ++attr_values; + } + + if (lang_name == NULL) + return; + + if (ccode != NULL) + { + g_hash_table_insert (lang_table, + g_strdup (ccode), + g_strdup (lang_name)); + } + if (ccode_longB != NULL) + { + g_hash_table_insert (lang_table, + g_strdup (ccode_longB), + g_strdup (lang_name)); + } + if (ccode_longT != NULL) + { + g_hash_table_insert (lang_table, + g_strdup (ccode_longT), + g_strdup (lang_name)); + } +} + +#define ISO_CODES_DATADIR ISO_CODES_PREFIX"/share/xml/iso-codes" +#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX"/share/locale" + +static void +totem_lang_table_init (void) +{ + GError *err = NULL; + char *buf; + gsize buf_len; + + lang_table = g_hash_table_new_full + (g_str_hash, g_str_equal, g_free, g_free); + + g_atexit (totem_lang_table_free); + + bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR); + bind_textdomain_codeset ("iso_639", "UTF-8"); + + if (g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml", + &buf, &buf_len, &err)) + { + GMarkupParseContext *ctx; + GMarkupParser parser = + { totem_lang_table_parse_start_tag, NULL, NULL, NULL, NULL }; + + ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL); + + if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) + { + g_warning ("Failed to parse '%s': %s\n", + ISO_CODES_DATADIR"/iso_639.xml", + err->message); + g_error_free (err); + } + + g_markup_parse_context_free (ctx); + g_free (buf); + } else { + g_warning ("Failed to load '%s': %s\n", + ISO_CODES_DATADIR"/iso_639.xml", err->message); + g_error_free (err); + } +} + +static const char * +totem_lang_get_full (const char *lang) +{ + const char *lang_name; + int len; + + g_return_val_if_fail (lang != NULL, NULL); + + len = strlen (lang); + if (len != 2 && len != 3) + return NULL; + if (lang_table == NULL) + totem_lang_table_init (); + + lang_name = (const gchar*) g_hash_table_lookup (lang_table, lang); + + if (lang_name) + return dgettext ("iso_639", lang_name); + + return NULL; +} + +/* Subtitle and language menus */ +static void +totem_g_list_deep_free (GList *list) +{ + GList *l; + + for (l = list; l != NULL; l = l->next) + g_free (l->data); + g_list_free (list); +} + +static void +subtitles_changed_callback (GtkRadioAction *action, GtkRadioAction *current, + Totem *totem) +{ + int rank; + + rank = gtk_radio_action_get_current_value (current); + + bacon_video_widget_set_subtitle (totem->bvw, rank); +} + + +static void +languages_changed_callback (GtkRadioAction *action, GtkRadioAction *current, + Totem *totem) +{ + int rank; + + rank = gtk_radio_action_get_current_value (current); + + bacon_video_widget_set_language (totem->bvw, rank); +} + +static GtkAction * +add_lang_action (Totem *totem, GtkActionGroup *action_group, guint ui_id, + const char *path, const char *prefix, const char *lang, + int lang_id, GSList **group) +{ + const char *full_lang; + char *label; + char *name; + GtkAction *action; + + full_lang = totem_lang_get_full (lang); + label = escape_label_for_menu (full_lang ? full_lang : lang); + name = g_strdup_printf ("%s-%d", prefix, lang_id); + + action = g_object_new (GTK_TYPE_RADIO_ACTION, + "name", name, + "label", label, + "value", lang_id, + NULL); + g_free (label); + + gtk_radio_action_set_group (GTK_RADIO_ACTION (action), *group); + *group = gtk_radio_action_get_group (GTK_RADIO_ACTION (action)); + gtk_action_group_add_action (action_group, action); + g_object_unref (action); + gtk_ui_manager_add_ui (totem->ui_manager, ui_id, + path, name, name, GTK_UI_MANAGER_MENUITEM, FALSE); + g_free (name); + + return action; +} + +static GtkAction * +create_lang_actions (Totem *totem, GtkActionGroup *action_group, guint ui_id, + const char *path, const char *prefix, GList *list, + gboolean is_lang) +{ + GtkAction *action = NULL; + unsigned int i, *hash_value; + GList *l; + GSList *group = NULL; + GHashTable *lookup; + char *action_data; + + if (is_lang == FALSE) { + add_lang_action (totem, action_group, ui_id, path, prefix, + _("None"), -2, &group); + } + + action = add_lang_action (totem, action_group, ui_id, path, prefix, + _("Auto"), -1, &group); + + i = 0; + lookup = g_hash_table_new_full (g_str_hash, g_int_equal, g_free, NULL); + + for (l = list; l != NULL; l = l->next) + { + hash_value = g_hash_table_lookup (lookup, l->data); + if (hash_value == NULL) { + action_data = g_strdup (l->data); + g_hash_table_insert (lookup, l->data, (unsigned int *)1); + } else { + action_data = g_strdup_printf ("%s #%u", (char *)l->data, (unsigned int)hash_value+1); + g_hash_table_replace (lookup, l->data, (unsigned int *)((unsigned int)hash_value+1)); + } + + add_lang_action (totem, action_group, ui_id, path, prefix, + action_data, i, &group); + i++; + } + + g_hash_table_destroy (lookup); + + return action; +} + +static gboolean +totem_sublang_equal_lists (GList *orig, GList *new) +{ + GList *o, *n; + gboolean retval; + + if ((orig == NULL && new != NULL) || (orig != NULL && new == NULL)) + return FALSE; + if (orig == NULL && new == NULL) + return TRUE; + + if (g_list_length (orig) != g_list_length (new)) + return FALSE; + + retval = TRUE; + o = orig; + n = new; + while (o != NULL && n != NULL && retval != FALSE) + { + if (g_str_equal (o->data, n->data) == FALSE) + retval = FALSE; + o = g_list_next (o); + n = g_list_next (n); + } + + return retval; +} + +static void +totem_languages_update (Totem *totem, GList *list) +{ + GtkAction *action; + int current; + + /* Remove old UI */ + gtk_ui_manager_remove_ui (totem->ui_manager, totem->languages_ui_id); + gtk_ui_manager_ensure_update (totem->ui_manager); + + /* Create new ActionGroup */ + if (totem->languages_action_group) { + gtk_ui_manager_remove_action_group (totem->ui_manager, + totem->languages_action_group); + g_object_unref (totem->languages_action_group); + } + totem->languages_action_group = gtk_action_group_new ("languages-action-group"); + gtk_ui_manager_insert_action_group (totem->ui_manager, + totem->languages_action_group, -1); + + if (list != NULL) + { + action = create_lang_actions (totem, totem->languages_action_group, + totem->languages_ui_id, + "/tmw-menubar/sound/languages/placeholder", + "languages", list, TRUE); + gtk_ui_manager_ensure_update (totem->ui_manager); + + current = bacon_video_widget_get_language (totem->bvw); + gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), + current); + g_signal_connect (G_OBJECT (action), "changed", + G_CALLBACK (languages_changed_callback), totem); + } + + totem_g_list_deep_free (totem->language_list); + totem->language_list = list; +} + +static void +totem_subtitles_update (Totem *totem, GList *list) +{ + GtkAction *action; + int current; + + /* Remove old UI */ + gtk_ui_manager_remove_ui (totem->ui_manager, totem->subtitles_ui_id); + gtk_ui_manager_ensure_update (totem->ui_manager); + + /* Create new ActionGroup */ + if (totem->subtitles_action_group) { + gtk_ui_manager_remove_action_group (totem->ui_manager, + totem->subtitles_action_group); + g_object_unref (totem->subtitles_action_group); + } + totem->subtitles_action_group = gtk_action_group_new ("subtitles-action-group"); + gtk_ui_manager_insert_action_group (totem->ui_manager, + totem->subtitles_action_group, -1); + + + if (list != NULL) + { + action = create_lang_actions (totem, totem->subtitles_action_group, + totem->subtitles_ui_id, + "/tmw-menubar/view/subtitles/placeholder", + "subtitles", list, FALSE); + gtk_ui_manager_ensure_update (totem->ui_manager); + + current = bacon_video_widget_get_subtitle (totem->bvw); + gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), + current); + g_signal_connect (G_OBJECT (action), "changed", + G_CALLBACK (subtitles_changed_callback), totem); + } + + totem_g_list_deep_free (totem->subtitles_list); + totem->subtitles_list = list; +} + +void +totem_sublang_update (Totem *totem) +{ + GList *list; + + list = bacon_video_widget_get_languages (totem->bvw); + if (totem_sublang_equal_lists (totem->language_list, list) == TRUE) { + totem_g_list_deep_free (list); + } else { + totem_languages_update (totem, list); + } + + list = bacon_video_widget_get_subtitles (totem->bvw); + if (totem_sublang_equal_lists (totem->subtitles_list, list) == TRUE) { + totem_g_list_deep_free (list); + } else { + totem_subtitles_update (totem, list); + } +} + +void +totem_sublang_exit (Totem *totem) +{ + totem_g_list_deep_free (totem->subtitles_list); + totem_g_list_deep_free (totem->language_list); +} + +/* Recent files */ +static void +on_recent_file_item_activated (GtkAction *action, + Totem *totem) +{ + GtkRecentInfo *recent_info; + const gchar *uri; + gboolean playlist_changed; + guint end; + + recent_info = g_object_get_data (G_OBJECT (action), "recent-info"); + uri = gtk_recent_info_get_uri (recent_info); + + totem_signal_block_by_data (totem->playlist, totem); + + end = totem_playlist_get_last (totem->playlist); + playlist_changed = totem_playlist_add_mrl (totem->playlist, uri, NULL); + gtk_recent_manager_add_item (totem->recent_manager, uri); + + totem_signal_unblock_by_data (totem->playlist, totem); + + if (playlist_changed) + { + char *mrl; + + totem_playlist_set_current (totem->playlist, end + 1); + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } +} + +static gint +totem_compare_recent_items (GtkRecentInfo *a, GtkRecentInfo *b) +{ + gboolean has_totem_a, has_totem_b; + + has_totem_a = gtk_recent_info_has_group (a, "Totem"); + has_totem_b = gtk_recent_info_has_group (b, "Totem"); + + if (has_totem_a && has_totem_b) { + time_t time_a, time_b; + + time_a = gtk_recent_info_get_modified (a); + time_b = gtk_recent_info_get_modified (b); + + return (time_b - time_a); + } else if (has_totem_a) { + return -1; + } else if (has_totem_b) { + return 1; + } + + return 0; +} + +/* Copied from eel so we don't have a dependency on them. + * It's being consolidated into glib eventually anyway. + */ +static char * +totem_str_middle_truncate (const char *string, + guint truncate_length) +{ + char *truncated; + guint length; + guint num_left_chars; + guint num_right_chars; + + const char delimter[] = "..."; + const guint delimter_length = strlen (delimter); + const guint min_truncate_length = delimter_length + 2; + + if (string == NULL) { + return NULL; + } + + /* It doesnt make sense to truncate strings to less than + * the size of the delimiter plus 2 characters (one on each + * side) + */ + if (truncate_length < min_truncate_length) { + return g_strdup (string); + } + + length = strlen (string); + + /* Make sure the string is not already small enough. */ + if (length <= truncate_length) { + return g_strdup (string); + } + + /* Find the 'middle' where the truncation will occur. */ + num_left_chars = (truncate_length - delimter_length) / 2; + num_right_chars = truncate_length - num_left_chars - delimter_length + 1; + + truncated = g_new (char, truncate_length + 1); + + strncpy (truncated, string, num_left_chars); + strncpy (truncated + num_left_chars, delimter, delimter_length); + strncpy (truncated + num_left_chars + delimter_length, string + length - num_right_chars + 1, num_right_chars); + + return truncated; +} + +static void +totem_recent_manager_changed_callback (GtkRecentManager *recent_manager, Totem *totem) +{ + GList *items, *l; + guint n_items = 0; + static guint i = 0; + + gtk_ui_manager_remove_ui (totem->ui_manager, totem->recent_ui_id); + gtk_ui_manager_ensure_update (totem->ui_manager); + + if (totem->recent_action_group) { + gtk_ui_manager_remove_action_group (totem->ui_manager, + totem->recent_action_group); + g_object_unref (totem->recent_action_group); + } + totem->recent_action_group = gtk_action_group_new ("recent-action-group"); + gtk_ui_manager_insert_action_group (totem->ui_manager, + totem->recent_action_group, -1); + + items = gtk_recent_manager_get_items (recent_manager); + items = g_list_sort (items, (GCompareFunc) totem_compare_recent_items); + + for (l = items; l && l->data; l = g_list_next (l)) { + GtkRecentInfo *info; + GtkAction *action; + char *action_name; + char *label; + char *escaped_label; + char *label_trunc; + const char *uri; + + info = (GtkRecentInfo *) l->data; + + if (!gtk_recent_info_has_group (info, "Totem")) + continue; + + action_name = g_strdup_printf ("recent-file%u", i++); + uri = gtk_recent_info_get_uri (info); + + /* Munge the URI for display */ + if (g_str_has_prefix (uri, "file:///") == FALSE) { + label_trunc = totem_str_middle_truncate (uri, TOTEM_MAX_RECENT_ITEM_LEN); + } else { + label_trunc = totem_str_middle_truncate + (gtk_recent_info_get_display_name (info), + TOTEM_MAX_RECENT_ITEM_LEN); + } + escaped_label = escape_label_for_menu (label_trunc); + g_free (label_trunc); + label = g_strdup_printf ("_%d. %s", n_items + 1, escaped_label); + g_free (escaped_label); + + action = g_object_new (GTK_TYPE_ACTION, + "name", action_name, + "label", label, + NULL); + + g_object_set_data_full (G_OBJECT (action), "recent-info", + gtk_recent_info_ref (info), + (GDestroyNotify) gtk_recent_info_unref); + g_signal_connect (G_OBJECT (action), "activate", + G_CALLBACK (on_recent_file_item_activated), + totem); + + gtk_action_group_add_action (totem->recent_action_group, + action); + g_object_unref (action); + + gtk_ui_manager_add_ui (totem->ui_manager, totem->recent_ui_id, + "/tmw-menubar/movie/recent-placeholder", + label, action_name, GTK_UI_MANAGER_MENUITEM, + FALSE); + + g_free (action_name); + g_free (label); + + if (++n_items == 5) + break; + } + gtk_ui_manager_ensure_update (totem->ui_manager); + + g_list_foreach (items, (GFunc) gtk_recent_info_unref, NULL); + g_list_free (items); +} + +void +totem_setup_recent (Totem *totem) +{ + totem->recent_manager = gtk_recent_manager_get_default (); + totem->recent_action_group = NULL; + totem->recent_ui_id = gtk_ui_manager_new_merge_id + (totem->ui_manager); + + g_signal_connect (G_OBJECT (totem->recent_manager), "changed", + G_CALLBACK (totem_recent_manager_changed_callback), + totem); + + totem_recent_manager_changed_callback (totem->recent_manager, totem); +} + +void +totem_action_add_recent (Totem *totem, const char *filename) +{ + GtkRecentData data; + char *groups[] = { NULL, NULL }; + + data.mime_type = gnome_vfs_get_mime_type (filename); + + if (strstr (filename, "file:///") == NULL) { + /* It's a URI/stream */ + groups[0] = "TotemStreams"; + data.display_name = NULL; + } else { + char *display; + + /* Local files with no mime-type probably don't exist */ + if (data.mime_type == NULL) + return; + + /* It's a local file */ + display = g_filename_from_uri (filename, NULL, NULL); + if (display) { + data.display_name = g_filename_display_basename (display); + g_free (display); + } + groups[0] = "Totem"; + } + + data.description = NULL; + data.app_name = g_strdup (g_get_application_name ()); + data.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL); + data.groups = groups; + gtk_recent_manager_add_full (totem->recent_manager, + filename, &data); + + if (data.display_name != NULL) + g_free (data.display_name); + g_free (data.mime_type); + g_free (data.app_name); + g_free (data.app_exec); +} + +/* Play Disc menu items */ + +static void +on_play_disc_activate (GtkAction *action, Totem *totem) +{ + char *device_path; + + device_path = g_object_get_data (G_OBJECT (action), "device_path"); + totem_action_play_media_device (totem, device_path); +} + +/* A GnomeVFSDrive and GnomeVFSVolume share many similar methods, but do not + share a base class other than GObject. */ +static char * +fake_gnome_vfs_device_get_something (GObject *device, + char *(*volume_function) (GnomeVFSVolume *), + char *(*drive_function) (GnomeVFSDrive *)) { + if (GNOME_IS_VFS_VOLUME (device)) { + return (*volume_function) (GNOME_VFS_VOLUME (device)); + } else if (GNOME_IS_VFS_DRIVE (device)) { + return (*drive_function) (GNOME_VFS_DRIVE (device)); + } else { + g_warning ("neither a GnomeVFSVolume or a GnomeVFSDrive"); + return NULL; + } +} + +static char * +my_gnome_vfs_volume_get_mount_path (GnomeVFSVolume *volume) +{ + char *uri, *path; + + uri = gnome_vfs_volume_get_activation_uri (volume); + path = g_filename_from_uri (uri, NULL, NULL); + g_free (uri); + + if (path == NULL) + return gnome_vfs_volume_get_device_path (volume); + return path; +} + +static void +add_device_to_menu (GObject *device, guint position, Totem *totem) +{ + char *name, *escaped_name, *icon_name, *device_path; + char *label, *activation_uri; + GtkAction *action; + gboolean disabled = FALSE; + + /* Add devices with blank CDs and audio CDs in them, but disable them */ + activation_uri = fake_gnome_vfs_device_get_something (device, + &gnome_vfs_volume_get_activation_uri, + &gnome_vfs_drive_get_activation_uri); + if (activation_uri != NULL) { + if (g_str_has_prefix (activation_uri, "burn://") != FALSE || g_str_has_prefix (activation_uri, "cdda://") != FALSE) { + disabled = TRUE; + } + g_free (activation_uri); + } else { + if (GNOME_IS_VFS_DRIVE (device)) { + device_path = gnome_vfs_drive_get_device_path + (GNOME_VFS_DRIVE (device)); + disabled = !totem_cd_has_medium (device_path); + g_free (device_path); + } + } + + name = fake_gnome_vfs_device_get_something (device, + &gnome_vfs_volume_get_display_name, + &gnome_vfs_drive_get_display_name); + icon_name = fake_gnome_vfs_device_get_something (device, + &gnome_vfs_volume_get_icon, &gnome_vfs_drive_get_icon); + device_path = fake_gnome_vfs_device_get_something (device, + &my_gnome_vfs_volume_get_mount_path, + &gnome_vfs_drive_get_device_path); + + g_strstrip (name); + escaped_name = escape_label_for_menu (name); + g_free (name); + label = g_strdup_printf (_("Play Disc '%s'"), escaped_name); + g_free (escaped_name); + + name = g_strdup_printf (_("device%d"), position); + action = gtk_action_new (name, label, NULL, NULL); + g_object_set (G_OBJECT (action), "icon-name", icon_name, + "sensitive", !disabled, NULL); + gtk_action_group_add_action (totem->devices_action_group, action); + g_object_unref (action); + gtk_ui_manager_add_ui (totem->ui_manager, totem->devices_ui_id, + "/tmw-menubar/movie/devices-placeholder", name, name, + GTK_UI_MANAGER_MENUITEM, FALSE); + g_free (name); + g_free (label); + g_free (icon_name); + + g_object_set_data_full (G_OBJECT (action), "device_path", + device_path, (GDestroyNotify) g_free); + if (GNOME_IS_VFS_VOLUME (device)) { + g_object_set_data_full (G_OBJECT (action), "activation_uri", + gnome_vfs_volume_get_activation_uri (GNOME_VFS_VOLUME (device)), + (GDestroyNotify) g_free); + } + + g_signal_connect (G_OBJECT (action), "activate", + G_CALLBACK (on_play_disc_activate), totem); +} + +static void +on_movie_menu_select (GtkMenuItem *movie_menuitem, Totem *totem) +{ + GList *devices, *volumes, *drives, *i; + guint position; + + if (totem->drives_changed == FALSE) + return; + + /* Remove old UI */ + gtk_ui_manager_remove_ui (totem->ui_manager, totem->devices_ui_id); + gtk_ui_manager_ensure_update (totem->ui_manager); + + /* Create new ActionGroup */ + if (totem->devices_action_group) { + gtk_ui_manager_remove_action_group (totem->ui_manager, + totem->devices_action_group); + g_object_unref (totem->devices_action_group); + } + totem->devices_action_group = gtk_action_group_new ("devices-action-group"); + gtk_ui_manager_insert_action_group (totem->ui_manager, + totem->devices_action_group, -1); + + /* Create a list of suitable devices */ + devices = NULL; + + volumes = gnome_vfs_volume_monitor_get_mounted_volumes + (totem->monitor); + for (i = volumes; i != NULL; i = i->next) { + if (gnome_vfs_volume_get_device_type (i->data) != GNOME_VFS_DEVICE_TYPE_CDROM) + continue; + + gnome_vfs_volume_ref (i->data); + devices = g_list_append (devices, i->data); + } + gnome_vfs_drive_volume_list_free (volumes); + + drives = gnome_vfs_volume_monitor_get_connected_drives (totem->monitor); + for (i = drives; i != NULL; i = i->next) { + if (gnome_vfs_drive_get_device_type (i->data) != GNOME_VFS_DEVICE_TYPE_CDROM) + continue; + else if (gnome_vfs_drive_is_mounted (i->data)) + continue; + + gnome_vfs_volume_ref (i->data); + devices = g_list_append (devices, i->data); + } + gnome_vfs_drive_volume_list_free (drives); + + /* Add the devices to the menu */ + position = 0; + + for (i = devices; i != NULL; i = i->next) + { + position++; + add_device_to_menu (i->data, position, totem); + } + gtk_ui_manager_ensure_update (totem->ui_manager); + + g_list_foreach (devices, (GFunc) g_object_unref, NULL); + g_list_free (devices); +} + +static void +on_gnome_vfs_monitor_event (GnomeVFSVolumeMonitor *monitor, + GnomeVFSDrive *drive, + Totem *totem) +{ + totem->drives_changed = TRUE; +} + +void +totem_setup_play_disc (Totem *totem) +{ + GtkWidget *item; + + item = gtk_ui_manager_get_widget (totem->ui_manager, "/tmw-menubar/movie"); + g_signal_connect (G_OBJECT (item), "select", + G_CALLBACK (on_movie_menu_select), totem); + + g_signal_connect (G_OBJECT (totem->monitor), + "drive-connected", + G_CALLBACK (on_gnome_vfs_monitor_event), totem); + g_signal_connect (G_OBJECT (totem->monitor), + "drive-disconnected", + G_CALLBACK (on_gnome_vfs_monitor_event), totem); + g_signal_connect (G_OBJECT (totem->monitor), + "volume-mounted", + G_CALLBACK (on_gnome_vfs_monitor_event), totem); + g_signal_connect (G_OBJECT (totem->monitor), + "volume-unmounted", + G_CALLBACK (on_gnome_vfs_monitor_event), totem); + + totem->drives_changed = TRUE; +} + +static void +open_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_open (totem); +} + +static void +open_location_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_open_location (totem); +} + +static void +eject_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_eject (totem); +} + +static void +properties_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_show_properties (totem); +} + +static void +play_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_play_pause (totem); +} + +static void +quit_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_exit (totem); +} + +static void +take_screenshot_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_take_screenshot (totem); +} + +static void +preferences_action_callback (GtkAction *action, Totem *totem) +{ + gtk_widget_show (totem->prefs); +} + +static void +fullscreen_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_fullscreen_toggle (totem); +} + +static void +zoom_1_2_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_set_scale_ratio (totem, 0.5); +} + +static void +zoom_1_1_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_set_scale_ratio (totem, 1); +} + +static void +zoom_2_1_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_set_scale_ratio (totem, 2); +} + +static void +zoom_in_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_zoom_relative (totem, ZOOM_IN_OFFSET); +} + +static void +zoom_reset_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_zoom_reset (totem); +} + +static void +zoom_out_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_zoom_relative (totem, ZOOM_OUT_OFFSET); +} + +static void +next_angle_action_callback (GtkAction *action, Totem *totem) +{ +} + +static void +dvd_root_menu_action_callback (GtkAction *action, Totem *totem) +{ + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU); +} + +static void +dvd_title_menu_action_callback (GtkAction *action, Totem *totem) +{ + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_TITLE_MENU); +} + +static void +dvd_audio_menu_action_callback (GtkAction *action, Totem *totem) +{ + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_AUDIO_MENU); +} + +static void +dvd_angle_menu_action_callback (GtkAction *action, Totem *totem) +{ + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ANGLE_MENU); +} + +static void +dvd_chapter_menu_action_callback (GtkAction *action, Totem *totem) +{ + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_CHAPTER_MENU); +} + +static void +next_chapter_action_callback (GtkAction *action, Totem *totem) +{ + TOTEM_PROFILE (totem_action_next (totem)); +} + +static void +previous_chapter_action_callback (GtkAction *action, Totem *totem) +{ + TOTEM_PROFILE (totem_action_previous (totem)); +} + +static void +skip_to_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_skip_to (totem); +} + +static void +skip_forward_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_seek_relative (totem, SEEK_FORWARD_OFFSET); +} + +static void +skip_backwards_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_seek_relative (totem, SEEK_BACKWARD_OFFSET); +} + +static void +volume_up_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_volume_relative (totem, VOLUME_UP_OFFSET); +} + +static void +volume_down_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_volume_relative (totem, VOLUME_DOWN_OFFSET); +} + +static void +contents_action_callback (GtkAction *action, Totem *totem) +{ + totem_action_show_help (totem); +} + +static void +about_action_callback (GtkAction *action, Totem *totem) +{ + char *backend_version, *description; + const char *frontend_type; + + const char *authors[] = + { + "Bastien Nocera <hadess@hadess.net>", + "Ronald Bultje <rbultje@ronald.bitfreak.net>", + "Julien Moutte <julien@moutte.net> (GStreamer backend)", + NULL + }; + const char *artists[] = { "Jakub Steiner <jimmac@ximian.com>", NULL }; + const char *documenters[] = + { + "Chee Bin Hoh <cbhoh@gnome.org>", + NULL + }; + char *license = totem_interface_get_license (); + +#ifdef HAVE_GTK_ONLY + frontend_type = N_("GTK+"); +#else + frontend_type = N_("GNOME"); +#endif + + backend_version = bacon_video_widget_get_backend_name (totem->bvw); + /* This lists the back-end and front-end types and versions, such as + * Movie Player using GStreamer 0.10.1 and GNOME */ + description = g_strdup_printf (_("Movie Player using %s and %s"), + backend_version, _(frontend_type)); + + gtk_show_about_dialog (GTK_WINDOW (totem->win), + "version", VERSION, + "copyright", _("Copyright \xc2\xa9 2002-2006 Bastien Nocera"), + "comments", description, + "authors", authors, + "documenters", documenters, + "artists", artists, + "translator-credits", _("translator-credits"), + "logo-icon-name", "totem", + "license", license, + "wrap-license", TRUE, + NULL); + + g_free (backend_version); + g_free (description); + g_free (license); +} + +static void +repeat_mode_action_callback (GtkToggleAction *action, Totem *totem) +{ + totem_playlist_set_repeat (totem->playlist, + gtk_toggle_action_get_active (action)); +} + +static void +shuffle_mode_action_callback (GtkToggleAction *action, Totem *totem) +{ + totem_playlist_set_shuffle (totem->playlist, + gtk_toggle_action_get_active (action)); +} + +static void +deinterlace_action_callback (GtkToggleAction *action, Totem *totem) +{ + gboolean value; + + value = gtk_toggle_action_get_active (action); + bacon_video_widget_set_deinterlacing (totem->bvw, value); + gconf_client_set_bool (totem->gc, GCONF_PREFIX"/deinterlace", + value, NULL); +} + +static void +always_on_top_action_callback (GtkToggleAction *action, Totem *totem) +{ + gtk_window_set_keep_above (GTK_WINDOW (totem->win), + gtk_toggle_action_get_active (action)); + gconf_client_set_bool (totem->gc, + GCONF_PREFIX"/window_on_top", + gtk_toggle_action_get_active (action), NULL); +} + +static void +show_controls_action_callback (GtkToggleAction *action, Totem *totem) +{ + gboolean show; + + show = gtk_toggle_action_get_active (action); + + /* Let's update our controls visibility */ + if (show) + totem->controls_visibility = TOTEM_CONTROLS_VISIBLE; + else + totem->controls_visibility = TOTEM_CONTROLS_HIDDEN; + + show_controls (totem, FALSE); +} + +static void +show_sidebar_action_callback (GtkToggleAction *action, Totem *totem) +{ + if (totem_is_fullscreen (totem)) + return; + + totem_sidebar_toggle (totem, gtk_toggle_action_get_active (action)); +} + +static void +aspect_ratio_changed_callback (GtkRadioAction *action, GtkRadioAction *current, Totem *totem) +{ + totem_action_set_aspect_ratio (totem, gtk_radio_action_get_current_value (current)); +} + +static void +clear_playlist_action_callback (GtkAction *action, Totem *totem) +{ + totem_playlist_clear (totem->playlist); + totem_action_set_mrl (totem, NULL); +} + +static const GtkActionEntry entries[] = { + { "movie-menu", NULL, N_("_Movie") }, + { "open", GTK_STOCK_OPEN, N_("_Open..."), "<control>O", N_("Open a file"), G_CALLBACK (open_action_callback) }, + { "open-location", NULL, N_("Open _Location..."), "<control>L", N_("Open a non-local file"), G_CALLBACK (open_location_action_callback) }, + { "eject", "media-eject", N_("_Eject"), "<control>E", NULL, G_CALLBACK (eject_action_callback) }, + { "properties", GTK_STOCK_PROPERTIES, N_("_Properties"), "<control>P", NULL, G_CALLBACK (properties_action_callback) }, + { "play", GTK_STOCK_MEDIA_PLAY, N_("Play / Pa_use"), "P", N_("Play or pause the movie"), G_CALLBACK (play_action_callback) }, + { "quit", GTK_STOCK_QUIT, N_("_Quit"), "<control>Q", N_("Quit the program"), G_CALLBACK (quit_action_callback) }, + + { "edit-menu", NULL, N_("_Edit") }, + { "take-screenshot", "camera-photo", N_("Take _Screenshot..."), "<control>S", N_("Take a screenshot"), G_CALLBACK (take_screenshot_action_callback) }, + { "clear-playlist", NULL, N_("_Clear Playlist"), NULL, N_("Clear playlist"), G_CALLBACK (clear_playlist_action_callback) }, + { "preferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL, NULL, G_CALLBACK (preferences_action_callback) }, + + { "view-menu", NULL, N_("_View") }, + { "fullscreen", "view-fullscreen", N_("_Fullscreen"), "F", N_("Switch to fullscreen"), G_CALLBACK (fullscreen_action_callback) }, + { "zoom-window-menu", NULL, N_("Fit Window to Movie") }, + { "zoom-1-2", NULL, N_("_Resize 1:2"), "0", N_("Resize to half the video size"), G_CALLBACK (zoom_1_2_action_callback) }, + { "zoom-1-1", NULL, N_("Resize _1:1"), "1", N_("Resize to video size"), G_CALLBACK (zoom_1_1_action_callback) }, + { "zoom-2-1", NULL, N_("Resize _2:1"), "2", N_("Resize to twice the video size"), G_CALLBACK (zoom_2_1_action_callback) }, + { "aspect-ratio-menu", NULL, N_("_Aspect Ratio") }, + { "next-angle", NULL, N_("Switch An_gles"), "G", N_("Switch angles"), G_CALLBACK (next_angle_action_callback) }, +/* { "subtitles-menu", NULL, N_("S_ubtitles") },*/ + + { "go-menu", NULL, N_("_Go") }, + { "dvd-root-menu", GTK_STOCK_INDEX, N_("_DVD Menu"), "m", N_("Go to the DVD menu"), G_CALLBACK (dvd_root_menu_action_callback) }, + { "dvd-title-menu", NULL, N_("_Title Menu"), NULL, N_("Go to the title menu"), G_CALLBACK (dvd_title_menu_action_callback) }, + { "dvd-audio-menu", NULL, N_("A_udio Menu"), NULL, N_("Go to the audio menu"), G_CALLBACK (dvd_audio_menu_action_callback) }, + { "dvd-angle-menu", NULL, N_("_Angle Menu"), NULL, N_("Go to the angle menu"), G_CALLBACK (dvd_angle_menu_action_callback) }, + { "dvd-chapter-menu", GTK_STOCK_INDEX, N_("_Chapter Menu"), "c", N_("Go to the chapter menu"), G_CALLBACK (dvd_chapter_menu_action_callback) }, + { "next-chapter", GTK_STOCK_MEDIA_NEXT, N_("_Next Chapter/Movie"), "n", N_("Next chapter or movie"), G_CALLBACK (next_chapter_action_callback) }, + { "previous-chapter", GTK_STOCK_MEDIA_PREVIOUS, N_("_Previous Chapter/Movie"), "b", N_("Previous chapter or movie"), G_CALLBACK (previous_chapter_action_callback) }, + { "skip-to", GTK_STOCK_JUMP_TO, N_("_Skip to..."), "S", N_("Skip to a specific time"), G_CALLBACK (skip_to_action_callback) }, + + { "sound-menu", NULL, N_("_Sound") }, +/* { "languages-menu", NULL, N_("_Languages") }, */ + { "volume-up", "audio-volume-high", N_("Volume _Up"), "Up", N_("Volume up"), G_CALLBACK (volume_up_action_callback) }, + { "volume-down", "audio-volume-low", N_("Volume _Down"), "Down", N_("Volume down"), G_CALLBACK (volume_down_action_callback) }, + + { "help-menu", NULL, N_("_Help") }, + { "contents", GTK_STOCK_HELP, N_("_Contents"), "F1", N_("Help contents"), G_CALLBACK (contents_action_callback) }, + { "about", GTK_STOCK_ABOUT, N_("_About"), NULL, NULL, G_CALLBACK (about_action_callback) } +}; + +static const GtkActionEntry zoom_entries[] = { + { "zoom-in", GTK_STOCK_ZOOM_IN, N_("Zoom In"), "R", N_("Zoom in"), G_CALLBACK (zoom_in_action_callback) }, + { "zoom-reset", GTK_STOCK_ZOOM_100, N_("Zoom Reset"), NULL, N_("Zoom reset"), G_CALLBACK (zoom_reset_action_callback) }, + { "zoom-out", GTK_STOCK_ZOOM_OUT, N_("Zoom Out"), "T", N_("Zoom out"), G_CALLBACK (zoom_out_action_callback) } +}; + +static const GtkActionEntry seek_entries_ltr[] = { + { "skip-forward", GTK_STOCK_MEDIA_FORWARD, N_("Skip _Forward"), "Right", N_("Skip forward"), G_CALLBACK (skip_forward_action_callback) }, + { "skip-backwards", GTK_STOCK_MEDIA_REWIND, N_("Skip _Backwards"), "Left", N_("Skip backwards"), G_CALLBACK (skip_backwards_action_callback) } +}; + +static const GtkActionEntry seek_entries_rtl[] = { + { "skip-forward", GTK_STOCK_MEDIA_FORWARD, N_("Skip _Forward"), "Left", N_("Skip forward"), G_CALLBACK (skip_forward_action_callback) }, + { "skip-backwards", GTK_STOCK_MEDIA_REWIND, N_("Skip _Backwards"), "Right", N_("Skip backwards"), G_CALLBACK (skip_backwards_action_callback) } +}; + +static const GtkToggleActionEntry toggle_entries[] = { + { "repeat-mode", NULL, N_("_Repeat Mode"), NULL, N_("Set the repeat mode"), G_CALLBACK (repeat_mode_action_callback), FALSE }, + { "shuffle-mode", NULL, N_("Shuff_le Mode"), NULL, N_("Set the shuffle mode"), G_CALLBACK (shuffle_mode_action_callback), FALSE }, + { "deinterlace", NULL, N_("_Deinterlace"), "I", N_("Deinterlace"), G_CALLBACK (deinterlace_action_callback), FALSE }, + { "always-on-top", NULL, N_("Always on _Top"), NULL, N_("Always on top"), G_CALLBACK (always_on_top_action_callback), FALSE }, + { "show-controls", NULL, N_("Show _Controls"), "H", N_("Show controls"), G_CALLBACK (show_controls_action_callback), TRUE }, + { "sidebar", NULL, N_("_Sidebar"), "F9", N_("Show or hide the sidebar"), G_CALLBACK (show_sidebar_action_callback), TRUE } +}; + +static const GtkRadioActionEntry aspect_ratio_entries[] = { + { "aspect-ratio-auto", NULL, N_("Auto"), NULL, N_("Sets automatic aspect ratio"), BVW_RATIO_AUTO }, + { "aspect-ratio-square", NULL, N_("Square"), NULL, N_("Sets square aspect ratio"), BVW_RATIO_SQUARE }, + { "aspect-ratio-fbt", NULL, N_("4:3 (TV)"), NULL, N_("Sets 4:3 (TV) aspect ratio"), BVW_RATIO_FOURBYTHREE }, + { "aspect-ratio-anamorphic", NULL, N_("16:9 (Widescreen)"), NULL, N_("Sets 16:9 (Anamorphic) aspect ratio"), BVW_RATIO_ANAMORPHIC }, + { "aspect-ratio-dvb", NULL, N_("2.11:1 (DVB)"), NULL, N_("Sets 2.11:1 (DVB) aspect ratio"), BVW_RATIO_DVB } +}; + +static void +totem_ui_manager_connect_proxy_callback (GtkUIManager *ui_manager, + GtkAction *action, GtkWidget *widget, Totem *totem) +{ + GtkRecentInfo *recent_info; + GdkPixbuf *icon; + GtkWidget *image; + gint w, h; + + recent_info = g_object_get_data (G_OBJECT (action), "recent-info"); + + if (recent_info == NULL) { + return; + } + + if (GTK_IS_IMAGE_MENU_ITEM (widget)) { + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h); + icon = gtk_recent_info_get_icon (recent_info, w); + + if (icon != NULL) { + image = gtk_image_new_from_pixbuf (icon); + gtk_image_menu_item_set_image + (GTK_IMAGE_MENU_ITEM (widget), image); + } + } +} + +void +totem_ui_manager_setup (Totem *totem) +{ + char *filename; + GtkWidget *menubar; + GtkWidget *menubar_box; + GtkAction *action; + + totem->main_action_group = gtk_action_group_new ("main-action-group"); + gtk_action_group_set_translation_domain (totem->main_action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (totem->main_action_group, entries, + G_N_ELEMENTS (entries), totem); + if (gtk_widget_get_direction (totem->win) == GTK_TEXT_DIR_RTL) { + gtk_action_group_add_actions (totem->main_action_group, + seek_entries_rtl, + G_N_ELEMENTS (seek_entries_rtl), totem); + } else { + gtk_action_group_add_actions (totem->main_action_group, + seek_entries_ltr, + G_N_ELEMENTS (seek_entries_ltr), totem); + } + gtk_action_group_add_toggle_actions (totem->main_action_group, + toggle_entries, G_N_ELEMENTS (toggle_entries), totem); + gtk_action_group_add_radio_actions (totem->main_action_group, + aspect_ratio_entries, G_N_ELEMENTS (aspect_ratio_entries), 0, + G_CALLBACK (aspect_ratio_changed_callback), totem); + + action = g_object_new (GTK_TYPE_ACTION, + "name", "subtitles-menu", + "label", _("S_ubtitles"), + "hide-if-empty", FALSE, NULL); + gtk_action_group_add_action (totem->main_action_group, action); + g_object_unref (action); + action = g_object_new (GTK_TYPE_ACTION, + "name", "languages-menu", + "label", _("_Languages"), + "hide-if-empty", FALSE, NULL); + gtk_action_group_add_action (totem->main_action_group, action); + g_object_unref (action); + + /* Hide help if we're using GTK+ only */ +#ifdef HAVE_GTK_ONLY + action = gtk_action_group_get_action + (totem->main_action_group, "contents"); + gtk_action_set_visible (action, FALSE); +#endif /* HAVE_GTK_ONLY */ + + totem->zoom_action_group = gtk_action_group_new ("zoom-action-group"); + gtk_action_group_set_translation_domain (totem->zoom_action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (totem->zoom_action_group, zoom_entries, + G_N_ELEMENTS (zoom_entries), totem); + + totem->ui_manager = gtk_ui_manager_new (); + g_signal_connect (G_OBJECT (totem->ui_manager), "connect-proxy", + G_CALLBACK (totem_ui_manager_connect_proxy_callback), + totem); + gtk_ui_manager_insert_action_group (totem->ui_manager, + totem->main_action_group, 0); + gtk_ui_manager_insert_action_group (totem->ui_manager, + totem->zoom_action_group, -1); + + totem->devices_action_group = NULL; + totem->devices_ui_id = gtk_ui_manager_new_merge_id (totem->ui_manager); + totem->languages_action_group = NULL; + totem->languages_ui_id = gtk_ui_manager_new_merge_id + (totem->ui_manager); + totem->subtitles_action_group = NULL; + totem->subtitles_ui_id = gtk_ui_manager_new_merge_id + (totem->ui_manager); + + gtk_window_add_accel_group (GTK_WINDOW (totem->win), + gtk_ui_manager_get_accel_group (totem->ui_manager)); + + filename = totem_interface_get_full_path ("totem-ui.xml"); + if (gtk_ui_manager_add_ui_from_file (totem->ui_manager, + filename, NULL) == 0) { + totem_interface_error_blocking ( + _("Couldn't load the 'ui description' file"), + _("Make sure that Totem is properly installed."), + GTK_WINDOW (totem->win)); + totem_action_exit (NULL); + } + g_free (filename); + + menubar = gtk_ui_manager_get_widget (totem->ui_manager, "/tmw-menubar"); + menubar_box = glade_xml_get_widget (totem->xml, "tmw_menubar_box"); + gtk_box_pack_start (GTK_BOX (menubar_box), menubar, FALSE, FALSE, 0); +} + diff --git a/trunk/src/totem-menu.h b/trunk/src/totem-menu.h new file mode 100644 index 000000000..229c4cd4d --- /dev/null +++ b/trunk/src/totem-menu.h @@ -0,0 +1,44 @@ +/* totem-menu.h + + Copyright (C) 2004-2005 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_MENU_H +#define TOTEM_MENU_H + +#include "totem.h" + +G_BEGIN_DECLS + +#define TOTEM_MAX_RECENT_ITEM_LEN 40 + +void totem_ui_manager_setup (Totem *totem); + +void totem_sublang_update (Totem *totem); +void totem_sublang_exit (Totem *totem); + +void totem_setup_play_disc (Totem *totem); + +void totem_setup_recent (Totem *totem); +void totem_action_add_recent (Totem *totem, const char *filename); + +G_END_DECLS + +#endif /* TOTEM_MENU_H */ diff --git a/trunk/src/totem-missing-plugins.c b/trunk/src/totem-missing-plugins.c new file mode 100644 index 000000000..bc2ebb164 --- /dev/null +++ b/trunk/src/totem-missing-plugins.c @@ -0,0 +1,267 @@ +/* totem-missing-plugins.c + + Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Tim-Philipp Müller <tim centricular net> + */ + +#include "config.h" + +#include "totem-missing-plugins.h" + +#ifdef USE_GIMME_CODEC + +#include "totem-private.h" +#include "bacon-video-widget.h" + +#include <gimme-codec.h> +#include <gst/gst.h> /* for gst_registry_update */ + +#include <gtk/gtk.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + +#include <string.h> + +GST_DEBUG_CATEGORY_EXTERN (_totem_gst_debug_cat); +#define GST_CAT_DEFAULT _totem_gst_debug_cat + +/* list of blacklisted detail strings */ +static GList *blacklisted_plugins = NULL; + +typedef struct +{ + gboolean playing; + gchar **descriptions; + gchar **details; + Totem *totem; +} +TotemCodecInstallContext; + +static gboolean +totem_codec_install_plugin_is_blacklisted (const gchar * detail) +{ + GList *res; + + res = g_list_find_custom (blacklisted_plugins, + detail, + (GCompareFunc) strcmp); + + return (res != NULL); +} + +static void +totem_codec_install_blacklist_plugin (const gchar * detail) +{ + if (!totem_codec_install_plugin_is_blacklisted (detail)) + { + blacklisted_plugins = g_list_prepend (blacklisted_plugins, + g_strdup (detail)); + } +} + +static void +totem_codec_install_context_free (TotemCodecInstallContext *ctx) +{ + g_strfreev (ctx->descriptions); + g_strfreev (ctx->details); + g_free (ctx); +} + +static void +on_gimme_codec_installation_done (GimmeCodecStatus status, void *user_data) +{ + TotemCodecInstallContext *ctx = (TotemCodecInstallContext *) user_data; + gchar **p; + + GST_INFO ("status = %d", status); + + /* FIXME: codes for PARTIAL_SUCCESS and USER_ABORTED will be added to + * libgimme-codec in the future */ + switch (status) + { + /* treat partial success the same as success; in the worst case we'll + * just do another round and get NOT_FOUND as result that time */ + /* case GIMME_CODEC_PARTIAL_SUCCESS: */ + case GIMME_CODEC_SUCCESS: + { + /* blacklist installed plugins too, so that we don't get + * into endless installer loops in case of inconsistencies */ + for (p = ctx->details; p != NULL && *p != NULL; ++p) + totem_codec_install_blacklist_plugin (*p); + + bacon_video_widget_stop (ctx->totem->bvw); + g_message ("Missing plugins installed. Updating plugin registry ..."); + + /* force GStreamer to re-read its plugin registry */ + if (gst_update_registry ()) + { + g_message ("Plugin registry updated, trying again."); + bacon_video_widget_play (ctx->totem->bvw, NULL); + } else { + g_warning ("GStreamer registry update failed"); + /* FIXME: should we show an error message here? */ + } + } + break; + case GIMME_CODEC_NOT_FOUND: + { + g_message ("No installation candidate for missing plugins found."); + + /* NOT_FOUND should only be returned if not a single one of the + * requested plugins was found; if we managed to play something + * anyway, we should just continue playing what we have and + * blacklist the requested plugins for this session; if we + * could not play anything we should blacklist them as well, + * so the install wizard isn't called again for nothing */ + for (p = ctx->details; p != NULL && *p != NULL; ++p) + totem_codec_install_blacklist_plugin (*p); + + if (ctx->playing) + { + bacon_video_widget_play (ctx->totem->bvw, NULL); + } else { + /* nothing we can do, user already saw error from wizard */ + bacon_video_widget_stop (ctx->totem->bvw); + } + } + break; + /* yet-to-be-added to libgimme-codec: + case GIMME_CODEC_USER_ABORTED: + { + if (ctx->playing) + bacon_video_widget_play (ctx->totem->bvw, NULL); + else + bacon_video_widget_stop (ctx->totem->bvw); + } + break; + */ + case GIMME_CODEC_INSTALLATION_ERROR: + case GIMME_CODEC_HELPER_CRASHED: + default: + { + g_message ("Missing plugin installation failed: %s", + gimme_codec_status_message (status)); + + if (ctx->playing) + bacon_video_widget_play (ctx->totem->bvw, NULL); + else + bacon_video_widget_stop (ctx->totem->bvw); + break; + } + } + + totem_codec_install_context_free (ctx); +} + +static gboolean +totem_on_missing_plugins_event (BaconVideoWidget *bvw, char **details, + char **descriptions, gboolean playing, + Totem *totem) +{ + TotemCodecInstallContext *ctx; + GimmeCodecStatus status; + guint i, num; + gulong xid = 0; + + gimme_codec_glib_init (); + + num = g_strv_length (details); + g_return_val_if_fail (num > 0 && g_strv_length (descriptions) == num, FALSE); + + ctx = g_new0 (TotemCodecInstallContext, 1); + ctx->descriptions = g_strdupv (descriptions); + ctx->details = g_strdupv (details); + ctx->playing = playing; + ctx->totem = totem; + + for (i = 0; i < num; ++i) + { + if (totem_codec_install_plugin_is_blacklisted (ctx->details[i])) + { + g_message ("Missing plugin: %s (ignoring)", ctx->details[i]); + g_free (ctx->details[i]); + g_free (ctx->descriptions[i]); + ctx->details[i] = ctx->details[num-1]; + ctx->descriptions[i] = ctx->descriptions[num-1]; + ctx->details[num-1] = NULL; + ctx->descriptions[num-1] = NULL; + --num; + --i; + } else { + g_message ("Missing plugin: %s (%s)", ctx->details[i], ctx->descriptions[i]); + } + } + + if (num == 0) + { + g_message ("All missing plugins are blacklisted, doing nothing"); + totem_codec_install_context_free (ctx); + return FALSE; + } + +#ifdef GDK_WINDOWING_X11 + if (totem->win != NULL && GTK_WIDGET_REALIZED (totem->win)) + xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (totem->win)->window); +#endif + + status = gimme_codec_async (ctx->details, xid, + on_gimme_codec_installation_done, + ctx); + + GST_INFO ("gimme_codec_async() status = %d", status); + + if (status != GIMME_CODEC_SUCCESS) + { + if (status == GIMME_CODEC_HELPER_MISSING) + { + g_message ("Automatic missing codec installation not supported " + "(helper script missing)"); + } else { + g_warning ("Failed to start codec installation: %s", + gimme_codec_status_message (status)); + } + totem_codec_install_context_free (ctx); + return FALSE; + } + + /* if we managed to start playing, pause playback, since some install + * wizard should now take over in a second anyway and the user might not + * be able to use totem's controls while the wizard is running */ + if (playing) + bacon_video_widget_pause (bvw); + + return TRUE; +} + +#endif /* USE_GIMME_CODEC */ + +void +totem_missing_plugins_setup (Totem *totem) +{ +#ifdef USE_GIMME_CODEC + g_signal_connect (G_OBJECT (totem->bvw), + "missing-plugins", + G_CALLBACK (totem_on_missing_plugins_event), + totem); + + GST_INFO ("Set up gimme-codec missing plugin support"); +#endif +} diff --git a/trunk/src/totem-missing-plugins.h b/trunk/src/totem-missing-plugins.h new file mode 100644 index 000000000..18672f5d6 --- /dev/null +++ b/trunk/src/totem-missing-plugins.h @@ -0,0 +1,34 @@ +/* totem-missing-plugins.h + + Copyright (C) 2007 Tim-Philipp Müller <tim centricular net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Tim-Philipp Müller <tim centricular net> + */ + +#ifndef TOTEM_MISSING_PLUGINS_H +#define TOTEM_MISSING_PLUGINS_H + +#include "totem.h" + +G_BEGIN_DECLS + +void totem_missing_plugins_setup (Totem *totem); + +G_END_DECLS + +#endif /* TOTEM_MISSING_PLUGINS_H */ diff --git a/trunk/src/totem-options.c b/trunk/src/totem-options.c new file mode 100644 index 000000000..ff6eadd97 --- /dev/null +++ b/trunk/src/totem-options.c @@ -0,0 +1,230 @@ +/* totem-options.c + + Copyright (C) 2004 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" + +#include <glib/gi18n.h> +#include <string.h> +#include <stdlib.h> + +#include "totem-remote.h" +#include "totem-options.h" +#include "totem-uri.h" +#include "bacon-video-widget.h" +#include "totem-private.h" + +TotemCmdLineOptions optionstate; /* Decoded command line options */ + +const GOptionEntry options[] = { + {"debug", '\0', 0, G_OPTION_ARG_NONE, &optionstate.debug, N_("Enable debug"), NULL}, + {"play-pause", '\0', 0, G_OPTION_ARG_NONE, &optionstate.playpause, N_("Play/Pause"), NULL}, + {"play", '\0', 0, G_OPTION_ARG_NONE, &optionstate.play, N_("Play"), NULL}, + {"pause", '\0', 0, G_OPTION_ARG_NONE, &optionstate.pause, N_("Pause"), NULL}, + {"next", '\0', 0, G_OPTION_ARG_NONE, &optionstate.next, N_("Next"), NULL}, + {"previous", '\0', 0, G_OPTION_ARG_NONE, &optionstate.previous, N_("Previous"), NULL}, + {"seek-fwd", '\0', 0, G_OPTION_ARG_NONE, &optionstate.seekfwd, N_("Seek Forwards"), NULL}, + {"seek-bwd", '\0', 0, G_OPTION_ARG_NONE, &optionstate.seekbwd, N_("Seek Backwards"), NULL}, + {"volume-up", '\0', 0, G_OPTION_ARG_NONE, &optionstate.volumeup, N_("Volume Up"), NULL}, + {"volume-down", '\0', 0, G_OPTION_ARG_NONE, &optionstate.volumedown, N_("Volume Down"), NULL}, + {"fullscreen", '\0', 0, G_OPTION_ARG_NONE, &optionstate.fullscreen, N_("Toggle Fullscreen"), NULL}, + {"toggle-controls", '\0', 0, G_OPTION_ARG_NONE, &optionstate.togglecontrols, N_("Show/Hide Controls"), NULL}, + {"quit", '\0', 0, G_OPTION_ARG_NONE, &optionstate.quit, N_("Quit"), NULL}, + {"enqueue", '\0', 0, G_OPTION_ARG_NONE, &optionstate.enqueue, N_("Enqueue"), NULL}, + {"replace", '\0', 0, G_OPTION_ARG_NONE, &optionstate.replace, N_("Replace"), NULL}, + {"printplaying", '\0', 0, G_OPTION_ARG_NONE, &optionstate.printplaying, "Print playing movie", NULL}, //FIXME translate + {"seek", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT64, &optionstate.seek, N_("Seek"), NULL}, + {"playlist-idx", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_DOUBLE, &optionstate.playlistidx, N_("Playlist index"), NULL}, + {G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &optionstate.filenames, N_("Movies to play"), NULL}, + {NULL} /* end the list */ +}; + +void +totem_options_process_late (Totem *totem, const TotemCmdLineOptions* options) +{ + if (options->fullscreen) + totem_action_fullscreen_toggle (totem); + + if (options->togglecontrols) + totem_action_toggle_controls (totem); + + /* Handle --playlist-idx */ + totem->index = options->playlistidx; + + /* Handle --seek */ + totem->seek_to = options->seek; +} + +void +totem_options_process_early (GConfClient *gc, const TotemCmdLineOptions* options) +{ + if (options->debug) + { + gconf_client_set_bool (gc, GCONF_PREFIX"/debug", + TRUE, NULL); + } else if (options->quit) + { + /* If --quit is one of the commands, just quit */ + gdk_notify_startup_complete (); + exit (0); + } +} + +static void +totem_print_playing_cb (const gchar *msg, gpointer user_data) +{ + if (strcmp (msg, SHOW_PLAYING_NO_TRACKS) != 0) + g_print ("%s\n", msg); + exit (0); +} + +static char * +totem_option_create_line (int command) +{ + return g_strdup_printf ("%03d ", command); +} + +void +totem_options_process_for_server (BaconMessageConnection *conn, + const TotemCmdLineOptions* options) +{ + GList *commands, *l; + int default_action, i; + + commands = NULL; + default_action = TOTEM_REMOTE_COMMAND_REPLACE; + + /* We can only handle "printplaying" on its own */ + if (options->printplaying) + { + char *line; + GMainLoop *loop = g_main_loop_new (NULL, FALSE); + + line = totem_option_create_line (TOTEM_REMOTE_COMMAND_SHOW_PLAYING); + bacon_message_connection_set_callback (conn, + totem_print_playing_cb, loop); + bacon_message_connection_send (conn, line); + g_free (line); + + g_main_loop_run (loop); + return; + } + + /* Are we quitting ? */ + if (options->quit) { + char *line; + line = totem_option_create_line (TOTEM_REMOTE_COMMAND_QUIT); + bacon_message_connection_send (conn, line); + g_free (line); + return; + } + + /* Then handle the things that modify the playlist */ + if (options->replace && options->enqueue) { + /* FIXME translate that */ + g_warning ("Can't enqueue and replace at the same time"); + } else if (options->replace) { + default_action = TOTEM_REMOTE_COMMAND_REPLACE; + } else if (options->enqueue) { + default_action = TOTEM_REMOTE_COMMAND_ENQUEUE; + } + + /* Send the files to enqueue */ + for (i = 0; options->filenames[i] != NULL; i++) + { + char *line, *full_path; + full_path = totem_create_full_path (options->filenames[i]); + line = g_strdup_printf ("%03d %s", default_action, full_path); + bacon_message_connection_send (conn, line); + g_free (line); + g_free (full_path); + } + + if (options->playpause) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_PLAYPAUSE)); + } + + if (options->play) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_PLAY)); + } + + if (options->pause) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_PAUSE)); + } + + if (options->next) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_NEXT)); + } + + if (options->previous) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_PREVIOUS)); + } + + if (options->seekfwd) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_SEEK_FORWARD)); + } + + if (options->seekbwd) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_SEEK_BACKWARD)); + } + + if (options->volumeup) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_VOLUME_UP)); + } + + if (options->volumedown) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_VOLUME_DOWN)); + } + + if (options->fullscreen) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_FULLSCREEN)); + } + + if (options->togglecontrols) { + commands = g_list_append (commands, totem_option_create_line + (TOTEM_REMOTE_COMMAND_TOGGLE_CONTROLS)); + } + + /* No commands, no files, show ourselves */ + if (commands == NULL && options->filenames == NULL) { + char *line; + line = totem_option_create_line (TOTEM_REMOTE_COMMAND_SHOW); + bacon_message_connection_send (conn, line); + return; + } + + /* Send commands */ + for (l = commands; l != NULL; l = l->next) { + bacon_message_connection_send (conn, l->data); + g_free (l->data); + } +} + diff --git a/trunk/src/totem-options.h b/trunk/src/totem-options.h new file mode 100644 index 000000000..49cccffe1 --- /dev/null +++ b/trunk/src/totem-options.h @@ -0,0 +1,67 @@ +/* totem-options.h + + Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_OPTIONS_H +#define TOTEM_OPTIONS_H + +#include "totem.h" +#include "bacon-message-connection.h" + +G_BEGIN_DECLS + +/* Stores the state of the command line options */ +typedef struct +{ + gboolean debug; + gboolean playpause; + gboolean play; + gboolean pause; + gboolean next; + gboolean previous; + gboolean seekfwd; + gboolean seekbwd; + gboolean volumeup; + gboolean volumedown; + gboolean fullscreen; + gboolean togglecontrols; + gboolean quit; + gboolean enqueue; + gboolean replace; + gboolean printplaying; + gdouble playlistidx; + gint64 seek; + gchar **filenames; +} TotemCmdLineOptions; + +extern const GOptionEntry options[]; +extern TotemCmdLineOptions optionstate; + +void totem_options_process_early (GConfClient *gc, + const TotemCmdLineOptions* options); +void totem_options_process_late (Totem *totem, + const TotemCmdLineOptions* options); +void totem_options_process_for_server (BaconMessageConnection *conn, + const TotemCmdLineOptions* options); + +G_END_DECLS + +#endif /* TOTEM_SKIPTO_H */ diff --git a/trunk/src/totem-playlist.c b/trunk/src/totem-playlist.c new file mode 100644 index 000000000..d7fe953ab --- /dev/null +++ b/trunk/src/totem-playlist.c @@ -0,0 +1,2282 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* totem-playlist.c + + Copyright (C) 2002, 2003, 2004, 2005 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" +#include "totem-playlist.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gdk/gdkkeysyms.h> +#include <glade/glade.h> +#include <gconf/gconf-client.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> +#include <string.h> + +#include "totem-uri.h" +#include "totem-interface.h" +#include "video-utils.h" +#include "debug.h" + +#define PL_LEN (gtk_tree_model_iter_n_children (playlist->_priv->model, NULL)) + +static void ensure_shuffled (TotemPlaylist *playlist, gboolean shuffle); +static gboolean totem_playlist_add_one_mrl (TotemPlaylist *playlist, const char *mrl, const char *display_name); + +typedef gboolean (*PlaylistCallback) (TotemPlaylist *playlist, const char *mrl, + gpointer data); + +typedef struct { + char *mimetype; + PlaylistCallback func; +} PlaylistTypes; + +typedef struct { + const char *name; + const char *suffix; + TotemPlParserType type; +} PlaylistSaveType; + +struct TotemPlaylistPrivate +{ + GladeXML *xml; + + GtkWidget *treeview; + GtkTreeModel *model; + GtkTreePath *current; + GtkTreeSelection *selection; + TotemPlParser *parser; + + GtkActionGroup *action_group; + GtkUIManager *ui_manager; + + /* This is a scratch list for when we're removing files */ + GList *list; + + /* These is the current paths for the file selectors */ + char *path; + char *save_path; + + /* Shuffle mode */ + int *shuffled; + int current_shuffled, shuffle_len; + + GConfClient *gc; + + int x, y; + + guint disable_save_to_disk : 1; + + /* Repeat mode */ + guint repeat : 1; + + /* Reorder Flag */ + guint drag_started : 1; + + /* Drop disabled flag */ + guint drop_disabled : 1; + + /* Shuffle mode */ + guint shuffle : 1; +}; + +/* Signals */ +enum { + CHANGED, + ITEM_ACTIVATED, + ACTIVE_NAME_CHANGED, + CURRENT_REMOVED, + REPEAT_TOGGLED, + SHUFFLE_TOGGLED, + LAST_SIGNAL +}; + +enum { + PLAYING_COL, + FILENAME_COL, + URI_COL, + TITLE_CUSTOM_COL, + CACHE_TITLE_COL, + CACHE_ARTIST_COL, + CACHE_ALBUM_COL, + NUM_COLS +}; + +static PlaylistSaveType save_types [] = { + {".PLS", ".pls", TOTEM_PL_PARSER_PLS}, + {".M3U", ".m3u", TOTEM_PL_PARSER_M3U}, + {".M3U (DOS)", ".m3u", TOTEM_PL_PARSER_M3U_DOS}, + {".XSPF", ".xspf", TOTEM_PL_PARSER_XSPF}, +}; + +static int totem_playlist_table_signals[LAST_SIGNAL] = { 0 }; + +static const GtkTargetEntry target_table[] = { + { "text/uri-list", 0, 0 }, + { "_NETSCAPE_URL", 0, 1 }, +}; + +static void playlist_remove_action_activated (GtkAction *action, TotemPlaylist *playlist); +static void playlist_copy_location_action_activated (GtkAction *action, TotemPlaylist *playlist); + +static const GtkActionEntry entries[] = { + { "remove", GTK_STOCK_REMOVE, N_("_Remove"), NULL, N_("Remove file from playlist"), G_CALLBACK (playlist_remove_action_activated) }, + { "copy-location", GTK_STOCK_COPY, N_("_Copy Location"), NULL, N_("Copy the location to the clipboard"), G_CALLBACK (playlist_copy_location_action_activated) } +}; + +static void totem_playlist_class_init (TotemPlaylistClass *class); +static void totem_playlist_init (TotemPlaylist *playlist); + +static void init_treeview (GtkWidget *treeview, TotemPlaylist *playlist); + +#define totem_playlist_unset_playing(x) totem_playlist_set_playing(x, FALSE) + +G_DEFINE_TYPE(TotemPlaylist, totem_playlist, GTK_TYPE_VBOX) + +/* Helper functions */ +static gboolean +totem_playlist_gtk_tree_model_iter_previous (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + GtkTreePath *path; + gboolean ret; + + path = gtk_tree_model_get_path (tree_model, iter); + ret = gtk_tree_path_prev (path); + if (ret != FALSE) + gtk_tree_model_get_iter (tree_model, iter, path); + + gtk_tree_path_free (path); + return ret; +} + +static gboolean +totem_playlist_gtk_tree_path_equals (GtkTreePath *path1, GtkTreePath *path2) +{ + char *str1, *str2; + gboolean retval; + + if (path1 == NULL && path2 == NULL) + return TRUE; + if (path1 == NULL || path2 == NULL) + return FALSE; + + str1 = gtk_tree_path_to_string (path1); + str2 = gtk_tree_path_to_string (path2); + + if (strcmp (str1, str2) == 0) + retval = TRUE; + else + retval = FALSE; + + g_free (str1); + g_free (str2); + + return retval; +} + +static GtkWindow * +totem_playlist_get_toplevel (TotemPlaylist *playlist) +{ + return GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (playlist))); +} + +static void +totem_playlist_error (char *title, char *reason, TotemPlaylist *playlist) +{ + GtkWidget *error_dialog; + + error_dialog = + gtk_message_dialog_new (totem_playlist_get_toplevel (playlist), + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + title); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), + reason); + + gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5); + gtk_dialog_set_default_response (GTK_DIALOG (error_dialog), + GTK_RESPONSE_OK); + g_signal_connect (G_OBJECT (error_dialog), "destroy", G_CALLBACK + (gtk_widget_destroy), error_dialog); + g_signal_connect (G_OBJECT (error_dialog), "response", G_CALLBACK + (gtk_widget_destroy), error_dialog); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + + gtk_widget_show (error_dialog); +} + +/* This one returns a new string, in UTF8 even if the mrl is encoded + * in the locale's encoding + */ +static char * +totem_playlist_mrl_to_title (const gchar *mrl) +{ + char *filename_for_display, *filename, *unescaped; + + filename = g_path_get_basename (mrl); + unescaped = gnome_vfs_unescape_string_for_display (filename); + g_free (filename); + + filename_for_display = g_filename_to_utf8 (unescaped, + -1, /* length */ + NULL, /* bytes_read */ + NULL, /* bytes_written */ + NULL); /* error */ + + if (filename_for_display == NULL) + { + filename_for_display = g_locale_to_utf8 (unescaped, + -1, NULL, NULL, NULL); + if (filename_for_display == NULL) { + filename_for_display = g_filename_display_name + (unescaped); + } + g_free (unescaped); + return filename_for_display; + } + + g_free (unescaped); + + return filename_for_display; +} + +static gboolean +totem_playlist_is_media (const char *mrl) +{ + if (mrl == NULL) + return FALSE; + + if (g_str_has_prefix (mrl, "cdda:") != FALSE) + return TRUE; + if (g_str_has_prefix (mrl, "dvd:") != FALSE) + return TRUE; + if (g_str_has_prefix (mrl, "vcd:") != FALSE) + return TRUE; + if (g_str_has_prefix (mrl, "cd:") != FALSE) + return TRUE; + + return FALSE; +} + +static void +totem_playlist_update_save_button (TotemPlaylist *playlist) +{ + GtkWidget *button; + gboolean state; + + button = glade_xml_get_widget (playlist->_priv->xml, "save_button"); + state = (!playlist->_priv->disable_save_to_disk) && (PL_LEN != 0); + gtk_widget_set_sensitive (button, state); +} + +static char* +totem_playlist_create_full_path (const char *path) +{ + char *retval, *curdir, *curdir_withslash, *escaped; + + g_return_val_if_fail (path != NULL, NULL); + + if (strstr (path, "://") != NULL) + return g_strdup (path); + if (totem_playlist_is_media (path) != FALSE) + return g_strdup (path); + + if (path[0] == '/') + { + escaped = gnome_vfs_escape_path_string (path); + + retval = g_strdup_printf ("file://%s", escaped); + g_free (escaped); + return retval; + } + + curdir = g_get_current_dir (); + escaped = gnome_vfs_escape_path_string (curdir); + curdir_withslash = g_strdup_printf ("file://%s%s", + escaped, G_DIR_SEPARATOR_S); + g_free (escaped); + g_free (curdir); + + escaped = gnome_vfs_escape_path_string (path); + retval = gnome_vfs_uri_make_full_from_relative + (curdir_withslash, escaped); + g_free (curdir_withslash); + g_free (escaped); + + return retval; +} + +static void +totem_playlist_save_get_iter_func (GtkTreeModel *model, + GtkTreeIter *iter, char **uri, char **title, + gboolean *custom_title, gpointer user_data) +{ + gtk_tree_model_get (model, iter, + URI_COL, uri, + FILENAME_COL, title, + TITLE_CUSTOM_COL, custom_title, + -1); +} + +void +totem_playlist_save_current_playlist (TotemPlaylist *playlist, const char *output) +{ + totem_playlist_save_current_playlist_ext (playlist, output, TOTEM_PL_PARSER_PLS); +} + +void +totem_playlist_save_current_playlist_ext (TotemPlaylist *playlist, const char *output, TotemPlParserType type) +{ + GError *error = NULL; + gboolean retval; + + retval = totem_pl_parser_write (playlist->_priv->parser, + playlist->_priv->model, + totem_playlist_save_get_iter_func, + output, type, NULL, &error); + + if (retval == FALSE) + { + totem_playlist_error (_("Could not save the playlist"), + error->message, playlist); + g_error_free (error); + } +} + +static void +gtk_tree_selection_has_selected_foreach (GtkTreeModel *model, + GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + int *retval = (gboolean *)user_data; + *retval = TRUE; +} + +static gboolean +gtk_tree_selection_has_selected (GtkTreeSelection *selection) +{ + int retval, *boolean; + + retval = FALSE; + boolean = &retval; + gtk_tree_selection_selected_foreach (selection, + gtk_tree_selection_has_selected_foreach, + (gpointer) (boolean)); + + return retval; +} + +static void +drop_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + TotemPlaylist *playlist) +{ + GList *list, *p, *file_list; + + list = gnome_vfs_uri_list_parse ((char *)data->data); + + if (list == NULL) { + gtk_drag_finish (context, FALSE, FALSE, time); + return; + } + + p = list; + file_list = NULL; + + while (p != NULL) + { + file_list = g_list_prepend (file_list, + gnome_vfs_uri_to_string + ((const GnomeVFSURI*)(p->data), 0)); + p = p->next; + } + + gnome_vfs_uri_list_free (list); + file_list = g_list_reverse (file_list); + + if (file_list == NULL) + { + gtk_drag_finish (context, FALSE, FALSE, time); + return; + } + + for (p = file_list; p != NULL; p = p->next) + { + char *filename, *title; + + if (p->data == NULL) + continue; + + filename = totem_create_full_path (p->data); + title = NULL; + + if (info == 1) + { + g_free (p->data); + p = p->next; + if (p != NULL) { + if (g_str_has_prefix (p->data, "file:") != FALSE) + title = (char *)p->data + 5; + else + title = p->data; + } + } + + totem_playlist_add_mrl (playlist, p->data, title); + + g_free (filename); + g_free (p->data); + } + + g_list_free (file_list); + gtk_drag_finish (context, TRUE, FALSE, time); + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); +} + +static void +playlist_copy_location_action_activated (GtkAction *action, TotemPlaylist *playlist) +{ + GList *l; + GtkTreePath *path; + GtkClipboard *clip; + char *url; + GtkTreeIter iter; + + l = gtk_tree_selection_get_selected_rows (playlist->_priv->selection, + NULL); + path = l->data; + + gtk_tree_model_get_iter (playlist->_priv->model, &iter, path); + + gtk_tree_model_get (playlist->_priv->model, + &iter, + URI_COL, &url, + -1); + + /* Set both the middle-click and the super-paste buffers */ + clip = gtk_clipboard_get_for_display + (gdk_display_get_default(), GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clip, url, -1); + clip = gtk_clipboard_get_for_display + (gdk_display_get_default(), GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text (clip, url, -1); + g_free (url); + + g_list_foreach (l, (GFunc) gtk_tree_path_free, NULL); + g_list_free (l); +} + +static gboolean +playlist_show_popup_menu (TotemPlaylist *playlist, GdkEventButton *event) +{ + guint button = 0; + guint32 time; + GtkTreePath *path; + gint count; + GtkWidget *menu; + GtkAction *action; + + if (event != NULL) { + button = event->button; + time = event->time; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (playlist->_priv->treeview), + event->x, event->y, &path, NULL, NULL, NULL)) { + if (!gtk_tree_selection_path_is_selected (playlist->_priv->selection, path)) { + gtk_tree_selection_unselect_all (playlist->_priv->selection); + gtk_tree_selection_select_path (playlist->_priv->selection, path); + } + gtk_tree_path_free (path); + } else { + gtk_tree_selection_unselect_all (playlist->_priv->selection); + } + } else { + time = gtk_get_current_event_time (); + } + + count = gtk_tree_selection_count_selected_rows (playlist->_priv->selection); + + if (count == 0) { + return FALSE; + } + + action = gtk_action_group_get_action (playlist->_priv->action_group, "copy-location"); + gtk_action_set_sensitive (action, count == 1); + + menu = gtk_ui_manager_get_widget (playlist->_priv->ui_manager, "/totem-playlist-popup"); + + gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + button, time); + + return TRUE; +} + +static gboolean +treeview_button_pressed (GtkTreeView *treeview, GdkEventButton *event, + TotemPlaylist *playlist) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + return playlist_show_popup_menu (playlist, event); + } + + return FALSE; +} + +static gboolean +playlist_treeview_popup_menu (GtkTreeView *treeview, TotemPlaylist *playlist) +{ + return playlist_show_popup_menu (playlist, NULL); +} + +static void +totem_playlist_set_reorderable (TotemPlaylist *playlist, gboolean set) +{ + guint num_items, i; + + gtk_tree_view_set_reorderable + (GTK_TREE_VIEW (playlist->_priv->treeview), set); + + if (set != FALSE) + return; + + num_items = PL_LEN; + for (i = 0; i < num_items; i++) + { + GtkTreeIter iter; + char *index; + GtkTreePath *path; + gboolean playing; + + index = g_strdup_printf ("%d", i); + if (gtk_tree_model_get_iter_from_string + (playlist->_priv->model, + &iter, index) == FALSE) + { + g_free (index); + continue; + } + g_free (index); + + gtk_tree_model_get (playlist->_priv->model, &iter, PLAYING_COL, &playing, -1); + if (!playing) { + continue; + } + + /* Only emit the changed signal if we changed the ->current */ + path = gtk_tree_path_new_from_indices (i, -1); + if (gtk_tree_path_compare (path, playlist->_priv->current) == 0) + { + gtk_tree_path_free (path); + } else { + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = path; + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], + 0, NULL); + } + + break; + } +} + +static gboolean +button_press_cb (GtkWidget *treeview, GdkEventButton *event, gpointer data) +{ + TotemPlaylist *playlist = (TotemPlaylist *)data; + + if (playlist->_priv->drop_disabled) + return FALSE; + + playlist->_priv->drop_disabled = TRUE; + gtk_drag_dest_unset (treeview); + g_signal_handlers_block_by_func (treeview, (GFunc) drop_cb, data); + + totem_playlist_set_reorderable (playlist, TRUE); + + return FALSE; +} + +static gboolean +button_release_cb (GtkWidget *treeview, GdkEventButton *event, gpointer data) +{ + TotemPlaylist *playlist = (TotemPlaylist *)data; + + if (!playlist->_priv->drag_started && playlist->_priv->drop_disabled) + { + playlist->_priv->drop_disabled = FALSE; + totem_playlist_set_reorderable (playlist, FALSE); + gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_handlers_unblock_by_func (treeview, + (GFunc) drop_cb, data); + } + + return FALSE; +} + +static void +drag_begin_cb (GtkWidget *treeview, GdkDragContext *context, gpointer data) +{ + TotemPlaylist *playlist = (TotemPlaylist *)data; + + playlist->_priv->drag_started = TRUE; + + return; +} + +static void +drag_end_cb (GtkWidget *treeview, GdkDragContext *context, gpointer data) +{ + TotemPlaylist *playlist = (TotemPlaylist *)data; + + playlist->_priv->drop_disabled = FALSE; + playlist->_priv->drag_started = FALSE; + totem_playlist_set_reorderable (playlist, FALSE); + gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, target_table, + G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_handlers_unblock_by_func (treeview, (GFunc) drop_cb, data); + + return; +} + +static void +selection_changed (GtkTreeSelection *treeselection, TotemPlaylist *playlist) +{ + GtkWidget *remove_button, *up_button, *down_button; + gboolean sensitivity; + + remove_button = glade_xml_get_widget (playlist->_priv->xml, + "remove_button"); + up_button = glade_xml_get_widget (playlist->_priv->xml, "up_button"); + down_button = glade_xml_get_widget (playlist->_priv->xml, + "down_button"); + + if (gtk_tree_selection_has_selected (treeselection)) + sensitivity = TRUE; + else + sensitivity = FALSE; + + gtk_widget_set_sensitive (remove_button, sensitivity); + gtk_widget_set_sensitive (up_button, sensitivity); + gtk_widget_set_sensitive (down_button, sensitivity); +} + +/* This function checks if the current item is NULL, and try to update it + * as the first item of the playlist if so. It returns TRUE if there is a + * current item */ +static gboolean +update_current_from_playlist (TotemPlaylist *playlist) +{ + int indice; + + if (playlist->_priv->current != NULL) + return TRUE; + + if (PL_LEN != 0) + { + if (playlist->_priv->shuffle == FALSE) + { + indice = 0; + } else { + indice = playlist->_priv->shuffled[0]; + playlist->_priv->current_shuffled = 0; + } + + playlist->_priv->current = gtk_tree_path_new_from_indices + (indice, -1); + } else { + return FALSE; + } + + return TRUE; +} + +static void +totem_playlist_add_files (GtkWidget *widget, TotemPlaylist *playlist) +{ + GSList *filenames, *l; + + filenames = totem_add_files (totem_playlist_get_toplevel (playlist), + NULL); + if (filenames == NULL) + return; + + for (l = filenames; l != NULL; l = l->next) { + char *mrl; + + mrl = l->data; + totem_playlist_add_mrl (playlist, mrl, NULL); + g_free (mrl); + } + + g_slist_free (filenames); +} + +static void +totem_playlist_foreach_selected (GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + TotemPlaylist *playlist = (TotemPlaylist *)data; + GtkTreeRowReference *ref; + + /* We can't use gtk_list_store_remove() here + * So we build a list a RowReferences */ + ref = gtk_tree_row_reference_new (playlist->_priv->model, path); + playlist->_priv->list = g_list_prepend + (playlist->_priv->list, (gpointer) ref); +} + +static void +playlist_remove_files (TotemPlaylist *playlist) +{ + GtkTreeSelection *selection; + GtkTreeRowReference *ref; + gboolean is_selected = FALSE; + int next_pos; + + selection = gtk_tree_view_get_selection + (GTK_TREE_VIEW (playlist->_priv->treeview)); + if (selection == NULL) + return; + + gtk_tree_selection_selected_foreach (selection, + totem_playlist_foreach_selected, + (gpointer) playlist); + + /* If the current item is to change, we need to keep an static + * reference to it, TreeIter and TreePath don't allow that */ + if (playlist->_priv->current != NULL) + { + int *indices; + + ref = gtk_tree_row_reference_new (playlist->_priv->model, + playlist->_priv->current); + is_selected = gtk_tree_selection_path_is_selected (selection, + playlist->_priv->current); + + indices = gtk_tree_path_get_indices (playlist->_priv->current); + next_pos = indices[0]; + + gtk_tree_path_free (playlist->_priv->current); + } else { + ref = NULL; + next_pos = -1; + } + + /* We destroy the items, one-by-one from the list built above */ + while (playlist->_priv->list != NULL) + { + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_row_reference_get_path + ((GtkTreeRowReference *)(playlist->_priv->list->data)); + gtk_tree_model_get_iter (playlist->_priv->model, &iter, path); + gtk_tree_path_free (path); + gtk_list_store_remove (GTK_LIST_STORE (playlist->_priv->model), + &iter); + + gtk_tree_row_reference_free + ((GtkTreeRowReference *)(playlist->_priv->list->data)); + playlist->_priv->list = g_list_remove (playlist->_priv->list, + playlist->_priv->list->data); + } + g_list_free (playlist->_priv->list); + playlist->_priv->list = NULL; + + if (is_selected != FALSE) + { + /* The current item was removed from the playlist */ + if (next_pos != -1) + { + char *str; + GtkTreeIter iter; + GtkTreePath *cur; + + str = g_strdup_printf ("%d", next_pos); + cur = gtk_tree_path_new_from_string (str); + + if (gtk_tree_model_get_iter (playlist->_priv->model, + &iter, cur) == FALSE) + { + playlist->_priv->current = NULL; + gtk_tree_path_free (cur); + } else { + playlist->_priv->current = cur; + } + g_free (str); + } else { + playlist->_priv->current = NULL; + } + + playlist->_priv->current_shuffled = -1; + ensure_shuffled (playlist, playlist->_priv->shuffle); + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CURRENT_REMOVED], + 0, NULL); + } else { + if (ref != NULL) + { + /* The path to the current item changed */ + playlist->_priv->current = + gtk_tree_row_reference_get_path (ref); + gtk_tree_row_reference_free (ref); + } + + ensure_shuffled (playlist, playlist->_priv->shuffle); + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); + } + totem_playlist_update_save_button (playlist); + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (playlist->_priv->treeview)); +} + +static void +playlist_remove_button_clicked (GtkWidget *button, TotemPlaylist *playlist) +{ + playlist_remove_files (playlist); +} + +static void +playlist_remove_action_activated (GtkAction *action, TotemPlaylist *playlist) +{ + playlist_remove_files (playlist); +} + +static void +totem_playlist_save_playlist (TotemPlaylist *playlist, char *filename, gint active_format) +{ + PlaylistSaveType *cur = NULL; + guint i; + + if (active_format > 0) + totem_playlist_save_current_playlist_ext (playlist, filename, + save_types[active_format - 1].type); + else { + for (i = 0; i < G_N_ELEMENTS(save_types); i++) { + if (g_str_has_suffix (filename, save_types[i].suffix)) { + cur = &save_types[i]; + break; + } + } + if (cur == NULL) + totem_playlist_error (_("Could not save the playlist"), _("Unknown file extension."), playlist); + else + totem_playlist_save_current_playlist_ext (playlist, filename, cur->type); + } +} + +static GtkWidget * +totem_playlist_save_add_format_combo_box (GtkFileChooser *fc) +{ + GtkWidget *hbox, *label, *combo_box; + guint i; + + hbox = gtk_hbox_new (FALSE, 4); + label = gtk_label_new (_("Select playlist format:")); + gtk_widget_show (label); + + combo_box = gtk_combo_box_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), + _("By extension")); + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0); + for (i = 0; i < G_N_ELEMENTS(save_types); i++) { + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), + save_types[i].name); + } + gtk_widget_show (combo_box); + + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), combo_box, TRUE, TRUE, 0); + gtk_widget_show (hbox); + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (fc), hbox); + + atk_object_add_relationship (gtk_widget_get_accessible (label), + ATK_RELATION_LABEL_FOR, + gtk_widget_get_accessible (combo_box)); + atk_object_add_relationship (gtk_widget_get_accessible (combo_box), + ATK_RELATION_LABELLED_BY, + gtk_widget_get_accessible (label)); + + return combo_box; +} + +static void +totem_playlist_save_files (GtkWidget *widget, TotemPlaylist *playlist) +{ + GtkWidget *fs, *combo_box; + char *filename; + int response; + + fs = gtk_file_chooser_dialog_new (_("Save Playlist"), + totem_playlist_get_toplevel (playlist), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (fs), GTK_RESPONSE_ACCEPT); + gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (fs), FALSE); + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (fs), TRUE); + /* translators: Playlist is the default saved playlist filename, + * without the suffix */ + filename = g_strconcat (_("Playlist"), save_types[0].suffix, NULL); + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (fs), filename); + g_free (filename); + combo_box = totem_playlist_save_add_format_combo_box (GTK_FILE_CHOOSER (fs)); + + if (playlist->_priv->save_path != NULL) + { + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (fs), + playlist->_priv->save_path); + } + + response = gtk_dialog_run (GTK_DIALOG (fs)); + gtk_widget_hide (fs); + while (gtk_events_pending()) + gtk_main_iteration(); + + if (response == GTK_RESPONSE_ACCEPT) + { + char *filename; + gint active_format; + + filename = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (fs)); + active_format = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)); + + gtk_widget_destroy (fs); + + if (filename == NULL) + return; + + g_free (playlist->_priv->save_path); + playlist->_priv->save_path = g_path_get_dirname (filename); + + totem_playlist_save_playlist (playlist, filename, active_format); + g_free (filename); + } else { + gtk_widget_destroy (fs); + } +} + +static void +totem_playlist_move_files (TotemPlaylist *playlist, gboolean direction_up) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + GtkTreeRowReference *current; + GList *paths, *refs, *l; + int pos; + + selection = gtk_tree_view_get_selection + (GTK_TREE_VIEW (playlist->_priv->treeview)); + if (selection == NULL) + return; + + model = gtk_tree_view_get_model + (GTK_TREE_VIEW (playlist->_priv->treeview)); + store = GTK_LIST_STORE (model); + pos = -2; + refs = NULL; + + if (playlist->_priv->current != NULL) + { + current = gtk_tree_row_reference_new (model, + playlist->_priv->current); + } else { + current = NULL; + } + + /* Build a list of tree references */ + paths = gtk_tree_selection_get_selected_rows (selection, NULL); + for (l = paths; l != NULL; l = l->next) + { + GtkTreePath *path = l->data; + int cur_pos, *indices; + + refs = g_list_prepend (refs, + gtk_tree_row_reference_new (model, path)); + indices = gtk_tree_path_get_indices (path); + cur_pos = indices[0]; + if (pos == -2) + { + pos = cur_pos; + } else { + if (direction_up == FALSE) + pos = MAX (cur_pos, pos); + else + pos = MIN (cur_pos, pos); + } + } + g_list_foreach (paths, (GFunc) gtk_tree_path_free, NULL); + g_list_free (paths); + + /* Otherwise we reverse the items when moving down */ + if (direction_up != FALSE) + refs = g_list_reverse (refs); + + if (direction_up == FALSE) + pos = pos + 2; + else + pos = pos - 2; + + for (l = refs; l != NULL; l = l->next) + { + GtkTreeIter *position, current; + GtkTreeRowReference *ref = l->data; + GtkTreePath *path; + + if (pos < 0) + { + position = NULL; + } else { + char *str; + + str = g_strdup_printf ("%d", pos); + if (gtk_tree_model_get_iter_from_string (model, + &iter, str)) + position = &iter; + else + position = NULL; + + g_free (str); + } + + path = gtk_tree_row_reference_get_path (ref); + gtk_tree_model_get_iter (model, ¤t, path); + gtk_tree_path_free (path); + + if (direction_up == FALSE) + { + pos--; + gtk_list_store_move_before (store, ¤t, position); + } else { + gtk_list_store_move_after (store, ¤t, position); + pos++; + } + } + + g_list_foreach (refs, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free (refs); + + /* Update the current path */ + if (current != NULL) + { + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = gtk_tree_row_reference_get_path + (current); + gtk_tree_row_reference_free (current); + } + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); +} + +static void +totem_playlist_up_files (GtkWidget *widget, TotemPlaylist *playlist) +{ + totem_playlist_move_files (playlist, TRUE); +} + +static void +totem_playlist_down_files (GtkWidget *widget, TotemPlaylist *playlist) +{ + totem_playlist_move_files (playlist, FALSE); +} + +static int +totem_playlist_key_press (GtkWidget *win, GdkEventKey *event, TotemPlaylist *playlist) +{ + /* Special case some shortcuts */ + if (event->state != 0) { + if ((event->state & GDK_CONTROL_MASK) + && event->keyval == GDK_a) { + gtk_tree_selection_select_all + (playlist->_priv->selection); + return TRUE; + } + } + + /* If we have modifiers, and either Ctrl, Mod1 (Alt), or any + * of Mod3 to Mod5 (Mod2 is num-lock...) are pressed, we + * let Gtk+ handle the key */ + if (event->state != 0 + && ((event->state & GDK_CONTROL_MASK) + || (event->state & GDK_MOD1_MASK) + || (event->state & GDK_MOD3_MASK) + || (event->state & GDK_MOD4_MASK) + || (event->state & GDK_MOD5_MASK))) + return FALSE; + + if (event->keyval == GDK_Delete) + { + playlist_remove_files (playlist); + return TRUE; + } + + return FALSE; +} + +static void +set_playing_icon (GtkTreeViewColumn *column, GtkCellRenderer *renderer, + GtkTreeModel *model, GtkTreeIter *iter, TotemPlaylist *playlist) +{ + gboolean playing; + + gtk_tree_model_get (model, iter, PLAYING_COL, &playing, -1); + + g_object_set (renderer, "icon-name", + playing ? "audio-volume-medium" : NULL, NULL); +} + +static void +init_columns (GtkTreeView *treeview, TotemPlaylist *playlist) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + /* Playing pix */ + renderer = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_cell_data_func (column, renderer, + (GtkTreeCellDataFunc) set_playing_icon, playlist, NULL); + g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL); + gtk_tree_view_append_column (treeview, column); + + /* Labels */ + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", FILENAME_COL, NULL); +} + +static void +treeview_row_changed (GtkTreeView *treeview, GtkTreePath *arg1, + GtkTreeViewColumn *arg2, TotemPlaylist *playlist) +{ + if (totem_playlist_gtk_tree_path_equals + (arg1, playlist->_priv->current) != FALSE) + { + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[ITEM_ACTIVATED], 0, + NULL); + return; + } + + if (playlist->_priv->current != NULL) + { + totem_playlist_unset_playing (playlist); + gtk_tree_path_free (playlist->_priv->current); + } + + playlist->_priv->current = gtk_tree_path_copy (arg1); + + if (playlist->_priv->shuffle != FALSE) + { + int *indices, indice, i; + + indices = gtk_tree_path_get_indices (playlist->_priv->current); + indice = indices[0]; + + for (i = 0; i < PL_LEN; i++) + { + if (playlist->_priv->shuffled[i] == indice) + { + playlist->_priv->current_shuffled = i; + break; + } + } + } + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); + + if (playlist->_priv->drop_disabled) { + playlist->_priv->drop_disabled = FALSE; + totem_playlist_set_reorderable (playlist, FALSE); + gtk_drag_dest_set (GTK_WIDGET (treeview), GTK_DEST_DEFAULT_ALL, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_handlers_unblock_by_func (treeview, + (GFunc) drop_cb, playlist); + } +} + +static void +init_treeview (GtkWidget *treeview, TotemPlaylist *playlist) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + + /* the model */ + model = GTK_TREE_MODEL (gtk_list_store_new (NUM_COLS, + G_TYPE_BOOLEAN, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING)); + + /* the treeview */ + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), model); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (treeview), TRUE); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + g_object_unref (G_OBJECT (model)); + + init_columns (GTK_TREE_VIEW (treeview), playlist); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + g_signal_connect (G_OBJECT (selection), "changed", + G_CALLBACK (selection_changed), playlist); + g_signal_connect (G_OBJECT (treeview), "row-activated", + G_CALLBACK (treeview_row_changed), playlist); + g_signal_connect (G_OBJECT (treeview), "button-press-event", + G_CALLBACK (treeview_button_pressed), playlist); + g_signal_connect (G_OBJECT (treeview), "popup-menu", + G_CALLBACK (playlist_treeview_popup_menu), playlist); + + /* Drag'n'Drop */ + g_signal_connect (G_OBJECT (treeview), "drag_data_received", + G_CALLBACK (drop_cb), playlist); + g_signal_connect (G_OBJECT (treeview), "button_press_event", + G_CALLBACK (button_press_cb), playlist); + g_signal_connect (G_OBJECT (treeview), "button_release_event", + G_CALLBACK (button_release_cb), playlist); + g_signal_connect (G_OBJECT (treeview), "drag_begin", + G_CALLBACK (drag_begin_cb), playlist); + g_signal_connect (G_OBJECT (treeview), "drag_end", + G_CALLBACK (drag_end_cb), playlist); + gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + playlist->_priv->selection = selection; + + gtk_widget_show (treeview); +} + +static void +update_repeat_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, TotemPlaylist *playlist) +{ + gboolean repeat; + + repeat = gconf_value_get_bool (entry->value); + playlist->_priv->repeat = (repeat != FALSE); + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[REPEAT_TOGGLED], 0, + repeat, NULL); +} + +typedef struct { + int random; + int index; +} RandomData; + +static int +compare_random (gconstpointer ptr_a, gconstpointer ptr_b) +{ + RandomData *a = (RandomData *) ptr_a; + RandomData *b = (RandomData *) ptr_b; + + if (a->random < b->random) + return -1; + else if (a->random > b->random) + return 1; + else + return 0; +} + +static void +ensure_shuffled (TotemPlaylist *playlist, gboolean shuffle) +{ + RandomData data; + GArray *array; + int i, current; + int *indices; + + if (shuffle == FALSE || PL_LEN != playlist->_priv->shuffle_len) + { + g_free (playlist->_priv->shuffled); + playlist->_priv->shuffled = NULL; + } + + if (shuffle == FALSE || PL_LEN == 0) + return; + + if (playlist->_priv->current != NULL) + { + indices = gtk_tree_path_get_indices (playlist->_priv->current); + current = indices[0]; + } else { + current = -1; + } + + playlist->_priv->shuffled = g_new (int, PL_LEN); + playlist->_priv->shuffle_len = PL_LEN; + + array = g_array_sized_new (FALSE, FALSE, + sizeof (RandomData), PL_LEN); + + for (i = 0; i < PL_LEN; i++) + { + data.random = g_random_int_range (0, PL_LEN); + data.index = i; + + g_array_append_val (array, data); + } + + g_array_sort (array, compare_random); + + for (i = 0; i < PL_LEN; i++) + { + playlist->_priv->shuffled[i] + = g_array_index (array, RandomData, i).index; + + if (playlist->_priv->current != NULL + && playlist->_priv->shuffled[i] == current) + playlist->_priv->current_shuffled = i; + } + + g_array_free (array, TRUE); +} + +static void +update_shuffle_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, TotemPlaylist *playlist) +{ + gboolean shuffle; + + shuffle = gconf_value_get_bool (entry->value); + playlist->_priv->shuffle = shuffle; + ensure_shuffled (playlist, shuffle); + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[SHUFFLE_TOGGLED], 0, + shuffle, NULL); +} + +static void +update_lockdown (GConfClient *client, guint cnxn_id, + GConfEntry *entry, TotemPlaylist *playlist) +{ + playlist->_priv->disable_save_to_disk = gconf_client_get_bool + (playlist->_priv->gc, + "/desktop/gnome/lockdown/disable_save_to_disk", NULL) != FALSE; + totem_playlist_update_save_button (playlist); +} + +static void +init_config (TotemPlaylist *playlist) +{ + playlist->_priv->gc = gconf_client_get_default (); + + playlist->_priv->disable_save_to_disk = gconf_client_get_bool + (playlist->_priv->gc, + "/desktop/gnome/lockdown/disable_save_to_disk", NULL) != FALSE; + totem_playlist_update_save_button (playlist); + + gconf_client_add_dir (playlist->_priv->gc, GCONF_PREFIX, + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + gconf_client_notify_add (playlist->_priv->gc, GCONF_PREFIX"/repeat", + (GConfClientNotifyFunc) update_repeat_cb, + playlist, NULL, NULL); + gconf_client_notify_add (playlist->_priv->gc, GCONF_PREFIX"/shuffle", + (GConfClientNotifyFunc) update_shuffle_cb, + playlist, NULL, NULL); + + gconf_client_add_dir (playlist->_priv->gc, "/desktop/gnome/lockdown", + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + gconf_client_notify_add (playlist->_priv->gc, + "/desktop/gnome/lockdown/disable_save_to_disk", + (GConfClientNotifyFunc) update_lockdown, + playlist, NULL, NULL); + + playlist->_priv->repeat = gconf_client_get_bool (playlist->_priv->gc, + GCONF_PREFIX"/repeat", NULL) != FALSE; + playlist->_priv->shuffle = gconf_client_get_bool (playlist->_priv->gc, + GCONF_PREFIX"/shuffle", NULL) != FALSE; +} + +static void +totem_playlist_entry_parsed (TotemPlParser *parser, + const char *uri, const char *title, + const char *genre, TotemPlaylist *playlist) +{ + totem_playlist_add_one_mrl (playlist, uri, title); +} + +static void +totem_playlist_init (TotemPlaylist *playlist) +{ + playlist->_priv = g_new0 (TotemPlaylistPrivate, 1); + playlist->_priv->parser = totem_pl_parser_new (); + + totem_pl_parser_add_ignored_scheme (playlist->_priv->parser, "dvd:"); + totem_pl_parser_add_ignored_scheme (playlist->_priv->parser, "cdda:"); + totem_pl_parser_add_ignored_scheme (playlist->_priv->parser, "vcd:"); + totem_pl_parser_add_ignored_scheme (playlist->_priv->parser, "cd:"); + + g_signal_connect (G_OBJECT (playlist->_priv->parser), + "entry", + G_CALLBACK (totem_playlist_entry_parsed), + playlist); +} + +static void +totem_playlist_finalize (GObject *object) +{ + TotemPlaylist *playlist = TOTEM_PLAYLIST (object); + + if (playlist->_priv->current != NULL) + gtk_tree_path_free (playlist->_priv->current); + g_object_unref (playlist->_priv->parser); + + if (playlist->_priv->ui_manager != NULL) { + g_object_unref (G_OBJECT (playlist->_priv->ui_manager)); + } + + if (playlist->_priv->action_group != NULL) { + g_object_unref (G_OBJECT (playlist->_priv->action_group)); + } + + G_OBJECT_CLASS (totem_playlist_parent_class)->finalize (object); +} + +GtkWidget* +totem_playlist_new (void) +{ + TotemPlaylist *playlist; + GtkWidget *container, *item; + char *filename; + + playlist = TOTEM_PLAYLIST (g_object_new (TOTEM_TYPE_PLAYLIST, NULL)); + + playlist->_priv->xml = totem_interface_load ("playlist.glade", + _("playlist"), TRUE, NULL); + + if (playlist->_priv->xml == NULL) + { + totem_playlist_finalize (G_OBJECT (playlist)); + return NULL; + } + + /* popup menu */ + playlist->_priv->action_group = + gtk_action_group_new ("playlist-action-group"); + gtk_action_group_set_translation_domain (playlist->_priv->action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (playlist->_priv->action_group, entries, + G_N_ELEMENTS (entries), playlist); + + playlist->_priv->ui_manager = gtk_ui_manager_new (); + gtk_ui_manager_insert_action_group (playlist->_priv->ui_manager, + playlist->_priv->action_group, -1); + + filename = totem_interface_get_full_path ("playlist-ui.xml"); + if (!gtk_ui_manager_add_ui_from_file (playlist->_priv->ui_manager, filename, NULL)) { + totem_playlist_finalize (G_OBJECT (playlist)); + return NULL; + } + g_free (filename); + + /* Connect the buttons */ + item = glade_xml_get_widget (playlist->_priv->xml, "add_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (totem_playlist_add_files), + playlist); + item = glade_xml_get_widget (playlist->_priv->xml, "remove_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (playlist_remove_button_clicked), + playlist); + item = glade_xml_get_widget (playlist->_priv->xml, "save_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (totem_playlist_save_files), + playlist); + item = glade_xml_get_widget (playlist->_priv->xml, "up_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (totem_playlist_up_files), + playlist); + item = glade_xml_get_widget (playlist->_priv->xml, "down_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (totem_playlist_down_files), + playlist); + + gtk_widget_add_events (GTK_WIDGET (playlist), GDK_KEY_PRESS_MASK); + g_signal_connect (G_OBJECT (playlist), "key_press_event", + G_CALLBACK (totem_playlist_key_press), playlist); + + /* Reparent the vbox */ + item = glade_xml_get_widget (playlist->_priv->xml, "dialog-vbox1"); + container = glade_xml_get_widget (playlist->_priv->xml, "vbox4"); + g_object_ref (container); + gtk_container_remove (GTK_CONTAINER (item), container); + gtk_box_pack_start (GTK_BOX (playlist), + container, + TRUE, /* expand */ + TRUE, /* fill */ + 0); /* padding */ + g_object_unref (container); + + playlist->_priv->treeview = glade_xml_get_widget + (playlist->_priv->xml, "treeview1"); + init_treeview (playlist->_priv->treeview, playlist); + playlist->_priv->model = gtk_tree_view_get_model + (GTK_TREE_VIEW (playlist->_priv->treeview)); + + /* The configuration */ + init_config (playlist); + + gtk_widget_show_all (GTK_WIDGET (playlist)); + + return GTK_WIDGET (playlist); +} + +static gboolean +totem_playlist_add_one_mrl (TotemPlaylist *playlist, const char *mrl, + const char *display_name) +{ + GtkListStore *store; + GtkTreeIter iter; + char *filename_for_display, *uri; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + g_return_val_if_fail (mrl != NULL, FALSE); + + if (display_name == NULL) + { + filename_for_display = totem_playlist_mrl_to_title (mrl); + } else { + filename_for_display = g_strdup (display_name); + } + + uri = totem_playlist_create_full_path (mrl); + + D("totem_playlist_add_one_mrl (): %s %s %s\n", + filename_for_display, uri, display_name); + + store = GTK_LIST_STORE (playlist->_priv->model); + gtk_list_store_insert_with_values (store, &iter, G_MAXINT32, + PLAYING_COL, FALSE, + FILENAME_COL, filename_for_display, + URI_COL, uri, + TITLE_CUSTOM_COL, display_name ? TRUE : FALSE, + -1); + + g_free (filename_for_display); + g_free (uri); + + if (playlist->_priv->current == NULL + && playlist->_priv->shuffle == FALSE) + playlist->_priv->current = gtk_tree_model_get_path + (playlist->_priv->model, &iter); + ensure_shuffled (playlist, playlist->_priv->shuffle); + + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CHANGED], 0, + NULL); + totem_playlist_update_save_button (playlist); + + return TRUE; +} + +gboolean +totem_playlist_add_mrl (TotemPlaylist *playlist, const char *mrl, + const char *display_name) +{ + TotemPlParserResult res; + GtkWidget *parent; + + g_return_val_if_fail (mrl != NULL, FALSE); + + parent = GTK_WIDGET (totem_playlist_get_toplevel (playlist)); + totem_gdk_window_set_waiting_cursor (parent->window); + res = totem_pl_parser_parse (playlist->_priv->parser, mrl, TRUE); + gdk_window_set_cursor (parent->window, NULL); + + if (res == TOTEM_PL_PARSER_RESULT_UNHANDLED) + return totem_playlist_add_one_mrl (playlist, mrl, display_name); + if (res == TOTEM_PL_PARSER_RESULT_ERROR) + { + totem_playlist_error (_("Playlist error"), _("The playlist '%s' could not be parsed, it might be damaged."), playlist); + return FALSE; + } + if (res == TOTEM_PL_PARSER_RESULT_IGNORED) + return FALSE; + + return TRUE; +} + +gboolean +totem_playlist_clear (TotemPlaylist *playlist) +{ + GtkListStore *store; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + if (PL_LEN == 0) + return FALSE; + + store = GTK_LIST_STORE (playlist->_priv->model); + gtk_list_store_clear (store); + + if (playlist->_priv->current != NULL) + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = NULL; + + totem_playlist_update_save_button (playlist); + + return TRUE; +} + +static void +totem_playlist_clear_with_compare (TotemPlaylist *playlist, GCompareFunc func, + gconstpointer data) +{ + GList *list = NULL, *l; + guint num_items, i; + gboolean has_items; + + num_items = PL_LEN; + if (num_items == 0) + return; + + for (i = 0; i < num_items; i++) + { + GtkTreeIter iter; + char *index; + char *mrl; + + index = g_strdup_printf ("%d", i); + if (gtk_tree_model_get_iter_from_string + (playlist->_priv->model, + &iter, index) == FALSE) + { + g_free (index); + continue; + } + g_free (index); + + gtk_tree_model_get (playlist->_priv->model, &iter, + URI_COL, &mrl, -1); + + if ((* func) (mrl, data) != FALSE) + { + GtkTreePath *path; + GtkTreeRowReference *ref; + + path = gtk_tree_path_new_from_indices (i, -1); + ref = gtk_tree_row_reference_new + (playlist->_priv->model, path); + list = g_list_prepend (list, ref); + gtk_tree_path_free (path); + } + + g_free (mrl); + } + + has_items = (list != NULL); + + for (l = list; l != NULL; l = l->next) + { + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_row_reference_get_path (l->data); + gtk_tree_model_get_iter (playlist->_priv->model, &iter, path); + gtk_list_store_remove (GTK_LIST_STORE (playlist->_priv->model), + &iter); + gtk_tree_path_free (path); + gtk_tree_row_reference_free (l->data); + } + g_list_free (list); + + if (has_items != FALSE) { + playlist->_priv->current_shuffled = -1; + + ensure_shuffled (playlist, playlist->_priv->shuffle); + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = NULL; + g_signal_emit (G_OBJECT (playlist), + totem_playlist_table_signals[CURRENT_REMOVED], + 0, NULL); + } +} + +static int +totem_playlist_compare_with_prefix (gconstpointer a, gconstpointer b) +{ + const char *mrl = (const char *) a; + const char *prefix = (const char *) b; + + return g_str_has_prefix (mrl, prefix); +} + +void +totem_playlist_clear_with_prefix (TotemPlaylist *playlist, const char *prefix) +{ + totem_playlist_clear_with_compare (playlist, + (GCompareFunc) totem_playlist_compare_with_prefix, + (void *)prefix); +} + +static int +totem_playlist_compare_with_volume (gpointer a, gpointer b) +{ + GnomeVFSVolume *clear_volume = (GnomeVFSVolume *) b; + const char *mrl = (const char *) a; + + char *filename; + GnomeVFSVolume *volume; + GnomeVFSVolumeMonitor *monitor; + gboolean retval = FALSE; + + if (g_str_has_prefix (mrl, "file:///") == FALSE) + return FALSE; + + monitor = gnome_vfs_get_volume_monitor (); + + filename = g_filename_from_uri (mrl, NULL, NULL); + volume = gnome_vfs_volume_monitor_get_volume_for_path + (monitor, filename); + g_free (filename); + + if (volume == clear_volume) + retval = TRUE; + + gnome_vfs_volume_unref (volume); + + return retval; +} + +void +totem_playlist_clear_with_gnome_vfs_volume (TotemPlaylist *playlist, + GnomeVFSVolume *volume) +{ + totem_playlist_clear_with_compare (playlist, + (GCompareFunc) totem_playlist_compare_with_volume, + volume); +} + +char +*totem_playlist_get_current_mrl (TotemPlaylist *playlist) +{ + GtkTreeIter iter; + char *path; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), NULL); + + if (update_current_from_playlist (playlist) == FALSE) + return NULL; + + if (gtk_tree_model_get_iter (playlist->_priv->model, &iter, + playlist->_priv->current) == FALSE) + return NULL; + + gtk_tree_model_get (playlist->_priv->model, + &iter, + URI_COL, &path, + -1); + + return path; +} + +char +*totem_playlist_get_current_title (TotemPlaylist *playlist, gboolean *custom) +{ + GtkTreeIter iter; + char *path; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), NULL); + + if (update_current_from_playlist (playlist) == FALSE) + return NULL; + + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + gtk_tree_model_get (playlist->_priv->model, + &iter, + FILENAME_COL, &path, + TITLE_CUSTOM_COL, custom, + -1); + + return path; +} + +gboolean +totem_playlist_get_current_metadata (TotemPlaylist *playlist, + char **artist, + char **title, + char **album) +{ + GtkTreeIter iter; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + if (update_current_from_playlist (playlist) == FALSE) + return FALSE; + + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + *artist = NULL; + gtk_tree_model_get (playlist->_priv->model, + &iter, + CACHE_ARTIST_COL, artist, + CACHE_TITLE_COL, title, + CACHE_ALBUM_COL, album, + -1); + + return (*artist != NULL); +} + +gboolean +totem_playlist_has_previous_mrl (TotemPlaylist *playlist) +{ + GtkTreeIter iter; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + if (update_current_from_playlist (playlist) == FALSE) + return FALSE; + + if (playlist->_priv->repeat != FALSE) + return TRUE; + + if (playlist->_priv->shuffle == FALSE) + { + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + return totem_playlist_gtk_tree_model_iter_previous + (playlist->_priv->model, &iter); + } else { + if (playlist->_priv->current_shuffled == 0) + return FALSE; + } + + return TRUE; +} + +gboolean +totem_playlist_has_next_mrl (TotemPlaylist *playlist) +{ + GtkTreeIter iter; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + if (update_current_from_playlist (playlist) == FALSE) + return FALSE; + + if (playlist->_priv->repeat != FALSE) + return TRUE; + + if (playlist->_priv->shuffle == FALSE) + { + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + return gtk_tree_model_iter_next (playlist->_priv->model, &iter); + } else { + if (playlist->_priv->current_shuffled == PL_LEN - 1) + return FALSE; + } + + return TRUE; +} + +gboolean +totem_playlist_set_title (TotemPlaylist *playlist, const char *title, gboolean force) +{ + GtkListStore *store; + GtkTreeIter iter; + gboolean custom_title; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + if (update_current_from_playlist (playlist) == FALSE) + return FALSE; + + store = GTK_LIST_STORE (playlist->_priv->model); + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + if (&iter == NULL) + return FALSE; + + if (force == FALSE) { + gtk_tree_model_get (playlist->_priv->model, &iter, + TITLE_CUSTOM_COL, &custom_title, + -1); + if (custom_title != FALSE) + return TRUE; + } + + gtk_list_store_set (store, &iter, + FILENAME_COL, title, + TITLE_CUSTOM_COL, TRUE, + -1); + + g_signal_emit (playlist, + totem_playlist_table_signals[ACTIVE_NAME_CHANGED], 0); + + return TRUE; +} + +gboolean +totem_playlist_set_playing (TotemPlaylist *playlist, gboolean state) +{ + GtkListStore *store; + GtkTreeIter iter; + GtkTreePath *path; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + if (update_current_from_playlist (playlist) == FALSE) + return FALSE; + + store = GTK_LIST_STORE (playlist->_priv->model); + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + g_return_val_if_fail (&iter != NULL, FALSE); + + gtk_list_store_set (store, &iter, + PLAYING_COL, state, + -1); + + if (state == FALSE) + return TRUE; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (playlist->_priv->treeview), + path, NULL, + TRUE, 0.5, 0); + gtk_tree_path_free (path); + + return TRUE; +} + +void +totem_playlist_set_previous (TotemPlaylist *playlist) +{ + GtkTreeIter iter; + + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + if (totem_playlist_has_previous_mrl (playlist) == FALSE) + return; + + totem_playlist_unset_playing (playlist); + + if (playlist->_priv->shuffle == FALSE) + { + char *path; + + path = gtk_tree_path_to_string (playlist->_priv->current); + if (strcmp (path, "0") == 0) + { + totem_playlist_set_at_end (playlist); + g_free (path); + return; + } + g_free (path); + + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + totem_playlist_gtk_tree_model_iter_previous + (playlist->_priv->model, &iter); + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = gtk_tree_model_get_path + (playlist->_priv->model, &iter); + } else { + int indice; + + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current_shuffled--; + if (playlist->_priv->current_shuffled < 0) { + indice = playlist->_priv->shuffled[PL_LEN -1]; + playlist->_priv->current_shuffled = PL_LEN -1; + } else { + indice = playlist->_priv->shuffled[playlist->_priv->current_shuffled]; + } + playlist->_priv->current = gtk_tree_path_new_from_indices + (indice, -1); + } +} + +void +totem_playlist_set_next (TotemPlaylist *playlist) +{ + GtkTreeIter iter; + + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + if (totem_playlist_has_next_mrl (playlist) == FALSE) + { + totem_playlist_set_at_start (playlist); + return; + } + + totem_playlist_unset_playing (playlist); + + if (playlist->_priv->shuffle == FALSE) + { + gtk_tree_model_get_iter (playlist->_priv->model, + &iter, + playlist->_priv->current); + + gtk_tree_model_iter_next (playlist->_priv->model, &iter); + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = gtk_tree_model_get_path + (playlist->_priv->model, &iter); + } else { + int indice; + + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current_shuffled++; + if (playlist->_priv->current_shuffled == PL_LEN) + playlist->_priv->current_shuffled = 0; + indice = playlist->_priv->shuffled[playlist->_priv->current_shuffled]; + playlist->_priv->current = gtk_tree_path_new_from_indices + (indice, -1); + } +} + +gboolean +totem_playlist_get_repeat (TotemPlaylist *playlist) +{ + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + return playlist->_priv->repeat; +} + +void +totem_playlist_set_repeat (TotemPlaylist *playlist, gboolean repeat) +{ + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + gconf_client_set_bool (playlist->_priv->gc, GCONF_PREFIX"/repeat", + repeat, NULL); +} + +gboolean +totem_playlist_get_shuffle (TotemPlaylist *playlist) +{ + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), FALSE); + + return playlist->_priv->shuffle; +} + +void +totem_playlist_set_shuffle (TotemPlaylist *playlist, gboolean shuffle) +{ + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + gconf_client_set_bool (playlist->_priv->gc, GCONF_PREFIX"/shuffle", + shuffle, NULL); +} + +void +totem_playlist_set_at_start (TotemPlaylist *playlist) +{ + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + totem_playlist_unset_playing (playlist); + + if (playlist->_priv->current != NULL) + { + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = NULL; + } + update_current_from_playlist (playlist); +} + +void +totem_playlist_set_at_end (TotemPlaylist *playlist) +{ + int indice; + + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + totem_playlist_unset_playing (playlist); + + if (playlist->_priv->current != NULL) + { + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = NULL; + } + + if (PL_LEN) + { + if (playlist->_priv->shuffle == FALSE) + indice = PL_LEN - 1; + else + indice = playlist->_priv->shuffled[PL_LEN - 1]; + + playlist->_priv->current = gtk_tree_path_new_from_indices + (indice, -1); + } +} + +guint +totem_playlist_get_current (TotemPlaylist *playlist) +{ + char *path; + double index; + + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), 0); + + path = gtk_tree_path_to_string (playlist->_priv->current); + if (path == NULL) + return -1; + + index = g_ascii_strtod (path, NULL); + g_free (path); + + return index; +} + +guint +totem_playlist_get_last (TotemPlaylist *playlist) +{ + g_return_val_if_fail (TOTEM_IS_PLAYLIST (playlist), -1); + + return PL_LEN - 1; +} + +void +totem_playlist_set_current (TotemPlaylist *playlist, guint index) +{ + g_return_if_fail (TOTEM_IS_PLAYLIST (playlist)); + + if (index >= (guint) PL_LEN) + return; + + totem_playlist_unset_playing (playlist); + //FIXME problems when shuffled? + gtk_tree_path_free (playlist->_priv->current); + playlist->_priv->current = gtk_tree_path_new_from_indices (index, -1); +} + +static void +totem_playlist_class_init (TotemPlaylistClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = totem_playlist_finalize; + + /* Signals */ + totem_playlist_table_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlaylistClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + totem_playlist_table_signals[ITEM_ACTIVATED] = + g_signal_new ("item-activated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlaylistClass, item_activated), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + totem_playlist_table_signals[ACTIVE_NAME_CHANGED] = + g_signal_new ("active-name-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlaylistClass, active_name_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + totem_playlist_table_signals[CURRENT_REMOVED] = + g_signal_new ("current-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlaylistClass, + current_removed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + totem_playlist_table_signals[REPEAT_TOGGLED] = + g_signal_new ("repeat-toggled", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlaylistClass, + repeat_toggled), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + totem_playlist_table_signals[SHUFFLE_TOGGLED] = + g_signal_new ("shuffle-toggled", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlaylistClass, + shuffle_toggled), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); +} + diff --git a/trunk/src/totem-playlist.h b/trunk/src/totem-playlist.h new file mode 100644 index 000000000..d35885bb5 --- /dev/null +++ b/trunk/src/totem-playlist.h @@ -0,0 +1,124 @@ +/* totem-playlist.h: Simple playlist dialog + + Copyright (C) 2002, 2003, 2004, 2005 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_PLAYLIST_H +#define TOTEM_PLAYLIST_H + +#include <gtk/gtkvbox.h> +#include <libgnomevfs/gnome-vfs-volume.h> + +#include "totem-pl-parser.h" + +G_BEGIN_DECLS + +#define TOTEM_TYPE_PLAYLIST (totem_playlist_get_type ()) +#define TOTEM_PLAYLIST(obj) (GTK_CHECK_CAST ((obj), TOTEM_TYPE_PLAYLIST, TotemPlaylist)) +#define TOTEM_PLAYLIST_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_PLAYLIST, TotemPlaylistClass)) +#define TOTEM_IS_PLAYLIST(obj) (GTK_CHECK_TYPE ((obj), TOTEM_TYPE_PLAYLIST)) +#define TOTEM_IS_PLAYLIST_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_PLAYLIST)) + +typedef enum { + TOTEM_PLAYLIST_DIRECTION_NEXT, + TOTEM_PLAYLIST_DIRECTION_PREVIOUS +} TotemPlaylistDirection; + +typedef struct TotemPlaylist TotemPlaylist; +typedef struct TotemPlaylistClass TotemPlaylistClass; +typedef struct TotemPlaylistPrivate TotemPlaylistPrivate; + +struct TotemPlaylist { + GtkVBox parent; + TotemPlaylistPrivate *_priv; +}; + +struct TotemPlaylistClass { + GtkVBoxClass parent_class; + + void (*changed) (TotemPlaylist *playlist); + void (*item_activated) (TotemPlaylist *playlist); + void (*active_name_changed) (TotemPlaylist *playlist); + void (*current_removed) (TotemPlaylist *playlist); + void (*repeat_toggled) (TotemPlaylist *playlist, gboolean repeat); + void (*shuffle_toggled) (TotemPlaylist *playlist, gboolean toggled); +}; + +GtkType totem_playlist_get_type (void); +GtkWidget *totem_playlist_new (void); + +/* The application is responsible for checking that the mrl is correct + * Handles directories, m3u playlists, and shoutcast playlists + * @display_name is if you have a preferred display string for the mrl, + * NULL otherwise + */ +gboolean totem_playlist_add_mrl (TotemPlaylist *playlist, const char *mrl, + const char *display_name); + +void totem_playlist_save_current_playlist (TotemPlaylist *playlist, + const char *output); +void totem_playlist_save_current_playlist_ext (TotemPlaylist *playlist, + const char *output, TotemPlParserType type); + +/* totem_playlist_clear doesn't emit the current_removed signal, even if it does + * because the caller should know what to do after it's done with clearing */ +gboolean totem_playlist_clear (TotemPlaylist *playlist); +void totem_playlist_clear_with_prefix (TotemPlaylist *playlist, + const char *prefix); +void totem_playlist_clear_with_gnome_vfs_volume (TotemPlaylist *playlist, + GnomeVFSVolume *volume); +char *totem_playlist_get_current_mrl (TotemPlaylist *playlist); +char *totem_playlist_get_current_title (TotemPlaylist *playlist, + gboolean *custom); +gboolean totem_playlist_get_current_metadata (TotemPlaylist *playlist, + char **artist, + char **title, + char **album); +gboolean totem_playlist_set_title (TotemPlaylist *playlist, + const char *title, + gboolean force); + +#define totem_playlist_has_direction(playlist, direction) (direction == TOTEM_PLAYLIST_DIRECTION_NEXT ? totem_playlist_has_next_mrl (playlist) : totem_playlist_has_previous_mrl (playlist)) +gboolean totem_playlist_has_previous_mrl (TotemPlaylist *playlist); +gboolean totem_playlist_has_next_mrl (TotemPlaylist *playlist); + +#define totem_playlist_set_direction(playlist, direction) (direction == TOTEM_PLAYLIST_DIRECTION_NEXT ? totem_playlist_set_next (playlist) : totem_playlist_set_previous (playlist)) +void totem_playlist_set_previous (TotemPlaylist *playlist); +void totem_playlist_set_next (TotemPlaylist *playlist); + +gboolean totem_playlist_get_repeat (TotemPlaylist *playlist); +void totem_playlist_set_repeat (TotemPlaylist *playlist, gboolean repeat); + +gboolean totem_playlist_get_shuffle (TotemPlaylist *playlist); +void totem_playlist_set_shuffle (TotemPlaylist *playlist, + gboolean shuffle); + +gboolean totem_playlist_set_playing (TotemPlaylist *playlist, gboolean state); + +void totem_playlist_set_at_start (TotemPlaylist *playlist); +void totem_playlist_set_at_end (TotemPlaylist *playlist); + +guint totem_playlist_get_current (TotemPlaylist *playlist); +guint totem_playlist_get_last (TotemPlaylist *playlist); +void totem_playlist_set_current (TotemPlaylist *playlist, guint index); + +G_END_DECLS + +#endif /* TOTEM_PLAYLIST_H */ diff --git a/trunk/src/totem-preferences.c b/trunk/src/totem-preferences.c new file mode 100644 index 000000000..deda9963e --- /dev/null +++ b/trunk/src/totem-preferences.c @@ -0,0 +1,732 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001,2002,2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> +#include <gtk/gtkmessagedialog.h> +#include <glib/gi18n.h> +#include <string.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "totem.h" +#include "totem-private.h" +#include "totem-preferences.h" +#include "video-utils.h" +#include "totem-subtitle-encoding.h" + +#include "debug.h" + +static void +totem_action_info (char *reason, Totem *totem) +{ + GtkWidget *parent, *error_dialog; + + if (totem == NULL) + parent = NULL; + else + parent = totem->prefs; + + error_dialog = + gtk_message_dialog_new (GTK_WINDOW (parent), + GTK_DIALOG_MODAL, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "%s", reason); + gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5); + gtk_dialog_set_default_response (GTK_DIALOG (error_dialog), + GTK_RESPONSE_OK); + g_signal_connect (G_OBJECT (error_dialog), "destroy", G_CALLBACK + (gtk_widget_destroy), error_dialog); + g_signal_connect (G_OBJECT (error_dialog), "response", G_CALLBACK + (gtk_widget_destroy), error_dialog); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + + gtk_widget_show (error_dialog); +} + +static gboolean +ask_show_visuals (Totem *totem) +{ + GtkWidget *dialog; + int answer; + + dialog = + gtk_message_dialog_new (NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_YES_NO, + _("Enable visual effects?")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("It seems you are running Totem remotely.\n" + "Are you sure you want to enable the visual " + "effects?")); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_NO); + answer = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + return (answer == GTK_RESPONSE_YES ? TRUE : FALSE); +} + +static void +on_checkbutton1_toggled (GtkToggleButton *togglebutton, Totem *totem) +{ + gboolean value; + + value = gtk_toggle_button_get_active (togglebutton); + gconf_client_set_bool (totem->gc, GCONF_PREFIX"/auto_resize", + value, NULL); + bacon_video_widget_set_auto_resize + (BACON_VIDEO_WIDGET (totem->bvw), value); +} + +static void +totem_prefs_set_show_visuals (Totem *totem, gboolean value, gboolean warn) +{ + GtkWidget *item; + + gconf_client_set_bool (totem->gc, + GCONF_PREFIX"/show_vfx", value, NULL); + + item = glade_xml_get_widget (totem->xml, "tpw_visuals_type_label"); + gtk_widget_set_sensitive (item, value); + item = glade_xml_get_widget (totem->xml, + "tpw_visuals_type_combobox"); + gtk_widget_set_sensitive (item, value); + item = glade_xml_get_widget (totem->xml, "tpw_visuals_size_label"); + gtk_widget_set_sensitive (item, value); + item = glade_xml_get_widget (totem->xml, + "tpw_visuals_size_combobox"); + gtk_widget_set_sensitive (item, value); + + if (warn == FALSE) + { + bacon_video_widget_set_show_visuals + (BACON_VIDEO_WIDGET (totem->bvw), value); + return; + } + + if (bacon_video_widget_set_show_visuals + (BACON_VIDEO_WIDGET (totem->bvw), value) == FALSE) + { + totem_action_info (_("The change of this setting will only " + "take effect for the next movie, or " + "when Totem is restarted."), + totem); + } +} + +static void +on_checkbutton2_toggled (GtkToggleButton *togglebutton, Totem *totem) +{ + gboolean value; + + value = gtk_toggle_button_get_active (togglebutton); + + if (value != FALSE && totem_display_is_local () == FALSE) + { + if (ask_show_visuals (totem) == FALSE) + { + gconf_client_set_bool (totem->gc, + GCONF_PREFIX"/show_vfx", FALSE, NULL); + gtk_toggle_button_set_active (togglebutton, FALSE); + return; + } + } + + totem_prefs_set_show_visuals (totem, value, TRUE); +} + +static void +on_tvout_toggled (GtkToggleButton *togglebutton, Totem *totem) +{ + TvOutType type; + gboolean value; + + value = gtk_toggle_button_get_active (togglebutton); + if (value == FALSE) + return; + + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (togglebutton), + "tvout_type")); + bacon_video_widget_set_tv_out + (BACON_VIDEO_WIDGET (totem->bvw), type); +} + +static void +deinterlace_changed_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, Totem *totem) +{ + GtkAction *action; + + action = gtk_action_group_get_action (totem->main_action_group, + "deinterlace"); + + g_signal_handlers_block_matched (G_OBJECT (action), + G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, totem); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/deinterlace", NULL)); + + g_signal_handlers_unblock_matched (G_OBJECT (action), + G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, totem); +} + +static void +auto_resize_changed_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, Totem *totem) +{ + GtkWidget *item; + + item = glade_xml_get_widget (totem->xml, "tpw_display_checkbutton"); + g_signal_handlers_disconnect_by_func (G_OBJECT (item), + on_checkbutton1_toggled, totem); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item), + gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/auto_resize", NULL)); + + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_checkbutton1_toggled), totem); +} + +static void +show_vfx_changed_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, Totem *totem) +{ + GtkWidget *item; + + item = glade_xml_get_widget (totem->xml, "tpw_visuals_checkbutton"); + g_signal_handlers_disconnect_by_func (G_OBJECT (item), + on_checkbutton2_toggled, totem); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item), + gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/show_vfx", NULL)); + + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_checkbutton2_toggled), totem); +} + +static void +disable_save_to_disk_changed_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, Totem *totem) +{ + GtkWidget *item; + gboolean locked; + + locked = gconf_client_get_bool (totem->gc, + "/desktop/gnome/lockdown/disable_save_to_disk", NULL); + item = glade_xml_get_widget (totem->xml, + "tmw_take_screenshot_menu_item"); + gtk_widget_set_sensitive (item, !locked); +} + +static void +connection_combobox_changed (GtkComboBox *combobox, Totem *totem) +{ + int i; + + i = gtk_combo_box_get_active (combobox); + bacon_video_widget_set_connection_speed + (BACON_VIDEO_WIDGET (totem->bvw), i); +} + +static void +visual_menu_changed (GtkComboBox *combobox, Totem *totem) +{ + GList *list; + char *old_name, *name; + int i; + + i = gtk_combo_box_get_active (combobox); + list = bacon_video_widget_get_visuals_list (totem->bvw); + name = g_list_nth_data (list, i); + + old_name = gconf_client_get_string (totem->gc, + GCONF_PREFIX"/visual", NULL); + + if (old_name == NULL || strcmp (old_name, name) != 0) + { + gconf_client_set_string (totem->gc, GCONF_PREFIX"/visual", + name, NULL); + + if (bacon_video_widget_set_visuals (totem->bvw, name) != FALSE) + totem_action_info (_("Changing the visuals effect type will require a restart to take effect."), totem); + } + + g_free (old_name); +} + +static void +visual_quality_menu_changed (GtkComboBox *combobox, Totem *totem) +{ + int i; + + i = gtk_combo_box_get_active (combobox); + gconf_client_set_int (totem->gc, + GCONF_PREFIX"/visual_quality", i, NULL); + bacon_video_widget_set_visuals_quality (totem->bvw, i); +} + +static void +brightness_changed (GtkRange *range, Totem *totem) +{ + gdouble i; + + i = gtk_range_get_value (range); + bacon_video_widget_set_video_property (totem->bvw, + BVW_VIDEO_BRIGHTNESS, (int) i); +} + +static void +contrast_changed (GtkRange *range, Totem *totem) +{ + gdouble i; + + i = gtk_range_get_value (range); + bacon_video_widget_set_video_property (totem->bvw, + BVW_VIDEO_CONTRAST, (int) i); +} + +static void +saturation_changed (GtkRange *range, Totem *totem) +{ + gdouble i; + + i = gtk_range_get_value (range); + bacon_video_widget_set_video_property (totem->bvw, + BVW_VIDEO_SATURATION, (int) i); +} + +static void +hue_changed (GtkRange *range, Totem *totem) +{ + gdouble i; + + i = gtk_range_get_value (range); + bacon_video_widget_set_video_property (totem->bvw, + BVW_VIDEO_HUE, (int) i); +} + +static void +on_tpw_color_reset_clicked (GtkButton *button, Totem *totem) +{ + guint i; + char *scales[] = { + "tpw_bright_scale", + "tpw_contrast_scale", + "tpw_saturation_scale", + "tpw_hue_scale" + }; + + for (i = 0; i < G_N_ELEMENTS (scales); i++) { + GtkWidget *item; + item = glade_xml_get_widget (totem->xml, scales[i]); + gtk_range_set_value (GTK_RANGE (item), 65535/2); + } +} + +static void +audio_out_menu_changed (GtkComboBox *combobox, Totem *totem) +{ + BaconVideoWidgetAudioOutType audio_out; + gboolean need_restart; + + audio_out = gtk_combo_box_get_active (combobox); + need_restart = bacon_video_widget_set_audio_out_type (totem->bvw, audio_out); + if (need_restart != FALSE) { + totem_action_info (_("The change of audio output type will " + "only take effect when Totem is " + "restarted."), + totem); + } +} + +static void +on_font_set (GtkFontButton * fb, Totem * totem) +{ + const gchar *font; + + font = gtk_font_button_get_font_name (fb); + gconf_client_set_string (totem->gc, GCONF_PREFIX"/subtitle_font", + font, NULL); +} + +static void +on_encoding_set (GtkComboBox *cb, Totem *totem) +{ + const gchar *encoding; + + encoding = totem_subtitle_encoding_get_selected (cb); + if (encoding) + gconf_client_set_string (totem->gc, + GCONF_PREFIX"/subtitle_encoding", + encoding, NULL); +} + +static void +font_changed_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, Totem *totem) +{ + const gchar *font; + GtkWidget *item; + + item = glade_xml_get_widget (totem->xml, "font_sel_button"); + font = gconf_value_get_string (entry->value); + gtk_font_button_set_font_name (GTK_FONT_BUTTON (item), font); + bacon_video_widget_set_subtitle_font (totem->bvw, font); +} + +static void +encoding_changed_cb (GConfClient *client, guint cnxn_id, + GConfEntry *entry, Totem *totem) +{ + const gchar *encoding; + GtkWidget *item; + + item = glade_xml_get_widget (totem->xml, "subtitle_encoding_combo"); + encoding = gconf_value_get_string (entry->value); + totem_subtitle_encoding_set (GTK_COMBO_BOX(item), encoding); + bacon_video_widget_set_subtitle_encoding (totem->bvw, encoding); +} + +void +totem_setup_preferences (Totem *totem) +{ + GtkWidget *item, *menu; + GtkAction *action; + gboolean show_visuals, auto_resize, is_local, deinterlace; + int connection_speed, i; + char *visual, *font, *encoding; + GList *list, *l; + BaconVideoWidgetAudioOutType audio_out; + GConfValue *value; + + g_return_if_fail (totem->gc != NULL); + + is_local = totem_display_is_local (); + + gconf_client_add_dir (totem->gc, GCONF_PREFIX, + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + gconf_client_notify_add (totem->gc, GCONF_PREFIX"/auto_resize", + (GConfClientNotifyFunc) auto_resize_changed_cb, + totem, NULL, NULL); + gconf_client_notify_add (totem->gc, GCONF_PREFIX"/show_vfx", + (GConfClientNotifyFunc) show_vfx_changed_cb, + totem, NULL, NULL); + gconf_client_add_dir (totem->gc, "/desktop/gnome/lockdown", + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + gconf_client_notify_add (totem->gc, + "/desktop/gnome/lockdown/disable_save_to_disk", + (GConfClientNotifyFunc) + disable_save_to_disk_changed_cb, + totem, NULL, NULL); + + /* Work-around glade dialogue not parenting properly for + * On top windows */ + item = glade_xml_get_widget (totem->xml, "tpw_notebook"); + totem->prefs = gtk_dialog_new_with_buttons ("Preferences", + GTK_WINDOW (totem->win), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, + GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_has_separator (GTK_DIALOG (totem->prefs), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (totem->prefs), 5); + gtk_box_set_spacing (GTK_BOX(GTK_DIALOG(totem->prefs)->vbox), 2); + gtk_widget_reparent (item, GTK_DIALOG (totem->prefs)->vbox); + gtk_widget_show_all (GTK_DIALOG (totem->prefs)->vbox); + item = glade_xml_get_widget (totem->xml, "totem_preferences_window"); + gtk_widget_destroy (item); + + g_signal_connect (G_OBJECT (totem->prefs), "response", + G_CALLBACK (gtk_widget_hide), NULL); + g_signal_connect (G_OBJECT (totem->prefs), "delete-event", + G_CALLBACK (gtk_widget_hide), NULL); + + /* Auto-resize */ + auto_resize = gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/auto_resize", NULL); + item = glade_xml_get_widget (totem->xml, "tpw_display_checkbutton"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item), auto_resize); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_checkbutton1_toggled), totem); + bacon_video_widget_set_auto_resize + (BACON_VIDEO_WIDGET (totem->bvw), auto_resize); + + /* Connection Speed */ + connection_speed = bacon_video_widget_get_connection_speed (totem->bvw); + item = glade_xml_get_widget (totem->xml, "tpw_speed_combobox"); + gtk_combo_box_set_active (GTK_COMBO_BOX (item), connection_speed); + g_signal_connect (item, "changed", + G_CALLBACK (connection_combobox_changed), totem); + + /* Enable visuals */ + item = glade_xml_get_widget (totem->xml, "tpw_visuals_checkbutton"); + show_visuals = gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/show_vfx", NULL); + if (is_local == FALSE && show_visuals != FALSE) + show_visuals = ask_show_visuals (totem); + + gtk_toggle_button_set_active + (GTK_TOGGLE_BUTTON (item), show_visuals); + totem_prefs_set_show_visuals (totem, show_visuals, FALSE); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_checkbutton2_toggled), totem); + + /* Visuals list */ + list = bacon_video_widget_get_visuals_list (totem->bvw); + menu = gtk_menu_new (); + gtk_widget_show (menu); + + visual = gconf_client_get_string (totem->gc, + GCONF_PREFIX"/visual", NULL); + if (visual == NULL || strcmp (visual, "") == 0) { + g_free (visual); + visual = g_strdup ("goom"); + } + + item = glade_xml_get_widget (totem->xml, "tpw_visuals_type_combobox"); + g_signal_connect (G_OBJECT (item), "changed", + G_CALLBACK (visual_menu_changed), totem); + + i = 0; + for (l = list; l != NULL; l = l->next) + { + const char *name = l->data; + + gtk_combo_box_append_text (GTK_COMBO_BOX (item), name); + + if (strcmp (name, visual) == 0) + gtk_combo_box_set_active (GTK_COMBO_BOX (item), i); + + i++; + } + g_free (visual); + + /* Visualisation quality */ + i = gconf_client_get_int (totem->gc, + GCONF_PREFIX"/visual_quality", NULL); + bacon_video_widget_set_visuals_quality (totem->bvw, i); + item = glade_xml_get_widget (totem->xml, "tpw_visuals_size_combobox"); + gtk_combo_box_set_active (GTK_COMBO_BOX (item), i); + g_signal_connect (G_OBJECT (item), "changed", + G_CALLBACK (visual_quality_menu_changed), totem); + + /* Brightness */ + item = glade_xml_get_widget (totem->xml, "tpw_bright_scale"); + i = bacon_video_widget_get_video_property (totem->bvw, + BVW_VIDEO_BRIGHTNESS); + gtk_range_set_value (GTK_RANGE (item), (gdouble) i); + g_signal_connect (G_OBJECT (item), "value-changed", + G_CALLBACK (brightness_changed), totem); + + /* Contrast */ + item = glade_xml_get_widget (totem->xml, "tpw_contrast_scale"); + i = bacon_video_widget_get_video_property (totem->bvw, + BVW_VIDEO_CONTRAST); + gtk_range_set_value (GTK_RANGE (item), (gdouble) i); + g_signal_connect (G_OBJECT (item), "value-changed", + G_CALLBACK (contrast_changed), totem); + + /* Saturation */ + item = glade_xml_get_widget (totem->xml, "tpw_saturation_scale"); + i = bacon_video_widget_get_video_property (totem->bvw, + BVW_VIDEO_SATURATION); + gtk_range_set_value (GTK_RANGE (item), (gdouble) i); + g_signal_connect (G_OBJECT (item), "value-changed", + G_CALLBACK (saturation_changed), totem); + + /* Hue */ + item = glade_xml_get_widget (totem->xml, "tpw_hue_scale"); + i = bacon_video_widget_get_video_property (totem->bvw, + BVW_VIDEO_HUE); + gtk_range_set_value (GTK_RANGE (item), (gdouble) i); + g_signal_connect (G_OBJECT (item), "value-changed", + G_CALLBACK (hue_changed), totem); + + /* Reset colour balance */ + item = glade_xml_get_widget (totem->xml, "tpw_color_reset"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_tpw_color_reset_clicked), totem); + + /* Sound output type */ + item = glade_xml_get_widget (totem->xml, "tpw_sound_output_combobox"); + audio_out = bacon_video_widget_get_audio_out_type (totem->bvw); + gtk_combo_box_set_active (GTK_COMBO_BOX (item), audio_out); + g_signal_connect (G_OBJECT (item), "changed", + G_CALLBACK (audio_out_menu_changed), totem); + + /* This one is for the deinterlacing menu, not really our dialog + * but we do it anyway */ + action = gtk_action_group_get_action (totem->main_action_group, + "deinterlace"); + deinterlace = gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/deinterlace", NULL); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + deinterlace); + bacon_video_widget_set_deinterlacing (totem->bvw, deinterlace); + gconf_client_notify_add (totem->gc, GCONF_PREFIX"/deinterlace", + (GConfClientNotifyFunc) deinterlace_changed_cb, + totem, NULL, NULL); + + /* Always On Top */ + action = gtk_action_group_get_action (totem->main_action_group, + "always-on-top"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + gconf_client_get_bool (totem->gc, + GCONF_PREFIX"/window_on_top", NULL)); + + /* Save to disk Lockdown */ + action = gtk_action_group_get_action (totem->main_action_group, + "take-screenshot"); + gtk_action_set_sensitive (action, + !gconf_client_get_bool (totem->gc, + "/desktop/gnome/lockdown/disable_save_to_disk", + NULL)); + + /* Subtitle font selection */ + item = glade_xml_get_widget (totem->xml, "font_sel_button"); + g_signal_connect (item, "font-set", G_CALLBACK (on_font_set), totem); + gtk_font_button_set_title (GTK_FONT_BUTTON (item), + _("Select Subtitle Font")); + font = gconf_client_get_string (totem->gc, + GCONF_PREFIX"/subtitle_font", NULL); + if (font && strcmp (font, "") != 0) { + gtk_font_button_set_font_name (GTK_FONT_BUTTON (item), font); + bacon_video_widget_set_subtitle_font (totem->bvw, font); + } + g_free (font); + gconf_client_notify_add (totem->gc, GCONF_PREFIX"/subtitle_font", + (GConfClientNotifyFunc) font_changed_cb, + totem, NULL, NULL); + + /* Subtitle encoding selection */ + item = glade_xml_get_widget (totem->xml, "subtitle_encoding_combo"); + totem_subtitle_encoding_init (GTK_COMBO_BOX (item)); + g_signal_connect (item, "changed", G_CALLBACK (on_encoding_set), totem); + value = gconf_client_get_without_default (totem->gc, + GCONF_PREFIX"/subtitle_encoding", NULL); + /* Make sure the default is UTF-8 */ + if (value != NULL) { + if (gconf_value_get_string (value) == NULL) { + encoding = g_strdup ("UTF-8"); + } else { + encoding = g_strdup (gconf_value_get_string (value)); + if (encoding[0] == '\0') { + g_free (encoding); + encoding = g_strdup ("UTF-8"); + } + } + gconf_value_free (value); + } else { + encoding = g_strdup ("UTF-8"); + } + totem_subtitle_encoding_set (GTK_COMBO_BOX(item), encoding); + if (encoding && strcasecmp (encoding, "") != 0) { + bacon_video_widget_set_subtitle_encoding (totem->bvw, encoding); + } + g_free (encoding); + gconf_client_notify_add (totem->gc, GCONF_PREFIX"/subtitle_encoding", + (GConfClientNotifyFunc) encoding_changed_cb, + totem, NULL, NULL); + +} + +void +totem_preferences_tvout_setup (Totem *totem) +{ + GtkWidget *item; + TvOutType type; + const char *name; + + type = bacon_video_widget_get_tv_out (totem->bvw); + switch (type) + { + case TV_OUT_NONE: + name = "tpw_notvout_radio_button"; + break; + case TV_OUT_NVTV_PAL: + name = "tpw_nvtvpalmode_radio_button"; + break; + case TV_OUT_NVTV_NTSC: + name = "tpw_nvtvntscmode_radio_button"; + break; + default: + g_assert_not_reached (); + name = NULL; + } + + item = glade_xml_get_widget (totem->xml, name); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item), TRUE); + + item = glade_xml_get_widget (totem->xml, "tpw_notvout_radio_button"); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_tvout_toggled), totem); + g_object_set_data (G_OBJECT (item), "tvout_type", + GINT_TO_POINTER (TV_OUT_NONE)); + gtk_widget_set_sensitive(item, + bacon_video_widget_fullscreen_mode_available (totem->bvw, TV_OUT_NONE)); + + item = glade_xml_get_widget (totem->xml, "tpw_nvtvpalmode_radio_button"); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_tvout_toggled), totem); + g_object_set_data (G_OBJECT (item), "tvout_type", + GINT_TO_POINTER (TV_OUT_NVTV_PAL)); + gtk_widget_set_sensitive(item, + bacon_video_widget_fullscreen_mode_available (totem->bvw, TV_OUT_NVTV_PAL)); + + item = glade_xml_get_widget (totem->xml, "tpw_nvtvntscmode_radio_button"); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_tvout_toggled), totem); + g_object_set_data (G_OBJECT (item), "tvout_type", + GINT_TO_POINTER (TV_OUT_NVTV_NTSC)); + gtk_widget_set_sensitive(item, + bacon_video_widget_fullscreen_mode_available (totem->bvw, TV_OUT_NVTV_NTSC)); +} + +void +totem_preferences_visuals_setup (Totem *totem) +{ + char *visual; + + visual = gconf_client_get_string (totem->gc, + GCONF_PREFIX"/visual", NULL); + if (visual == NULL || strcmp (visual, "") == 0) { + g_free (visual); + visual = g_strdup ("goom"); + } + + bacon_video_widget_set_visuals (totem->bvw, visual); + g_free (visual); +} diff --git a/trunk/src/totem-preferences.h b/trunk/src/totem-preferences.h new file mode 100644 index 000000000..36f529e65 --- /dev/null +++ b/trunk/src/totem-preferences.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2001,2002,2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef TOTEM_PREFERENCES_H +#define TOTEM_PREFERENCES_H + +G_BEGIN_DECLS + +void totem_setup_preferences (Totem *totem); +void totem_preferences_tvout_setup (Totem *totem); +void totem_preferences_visuals_setup (Totem *totem); + +GtkWidget * bacon_cd_selection_create (void); + +G_END_DECLS + +#endif /* TOTEM_PREFERENCES_H */ diff --git a/trunk/src/totem-private.h b/trunk/src/totem-private.h new file mode 100644 index 000000000..780ee7598 --- /dev/null +++ b/trunk/src/totem-private.h @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2001-2002 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef __TOTEM_PRIVATE_H__ +#define __TOTEM_PRIVATE_H__ + +#include <glade/glade.h> +#include <gconf/gconf-client.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> + +#include "totem-remote.h" +#include "totem-scrsaver.h" +#include "totem-playlist.h" +#include "bacon-message-connection.h" +#include "bacon-video-widget.h" +#include "totem-skipto.h" + +#define totem_signal_block_by_data(obj, data) (g_signal_handlers_block_matched (obj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, data)) +#define totem_signal_unblock_by_data(obj, data) (g_signal_handlers_unblock_matched (obj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, data)) + +#define totem_set_sensitivity(xml, name, state) \ + { \ + GtkWidget *widget; \ + widget = glade_xml_get_widget (xml, name); \ + gtk_widget_set_sensitive (widget, state); \ + } +#define totem_main_set_sensitivity(name, state) totem_set_sensitivity (totem->xml, name, state) + +#define totem_action_set_sensitivity(name, state) \ + { \ + GtkAction *action; \ + action = gtk_action_group_get_action (totem->main_action_group, name); \ + gtk_action_set_sensitive (action, state); \ + } + +typedef enum { + TOTEM_CONTROLS_VISIBLE, + TOTEM_CONTROLS_HIDDEN, + TOTEM_CONTROLS_FULLSCREEN +} ControlsVisibility; + +typedef enum { + STATE_PLAYING, + STATE_PAUSED, + STATE_STOPPED +} TotemStates; + +struct Totem { + /* Control window */ + GladeXML *xml; + GtkWidget *win; + BaconVideoWidget *bvw; + GtkWidget *prefs; + GtkWidget *statusbar; + + /* UI manager */ + GtkActionGroup *main_action_group; + GtkActionGroup *zoom_action_group; + GtkUIManager *ui_manager; + + GtkActionGroup *devices_action_group; + guint devices_ui_id; + + GtkActionGroup *languages_action_group; + guint languages_ui_id; + + GtkActionGroup *subtitles_action_group; + guint subtitles_ui_id; + + /* Sidebar */ + GtkWidget *sidebar; + gboolean sidebar_shown; + int sidebar_w; + + /* Separate Dialogs */ + GtkWidget *properties; + TotemSkipto *skipto; + + /* Play/Pause */ + GtkWidget *pp_button; + /* fullscreen Play/Pause */ + GtkWidget *fs_pp_button; + GtkTooltips *tooltip; + + /* Seek */ + GtkWidget *seek; + GtkAdjustment *seekadj; + gboolean seek_lock; + gboolean seekable; + + /* Volume */ + GtkWidget *volume; + gboolean vol_lock; + gboolean vol_fs_lock; + gfloat prev_volume; + int volume_first_time; + gboolean volume_sensitive; + + /* Subtitles/Languages menus */ + GtkWidget *subtitles; + GtkWidget *languages; + GList *subtitles_list; + GList *language_list; + + /* exit fullscreen Popup */ + GtkWidget *exit_popup; + + /* controls management */ + ControlsVisibility controls_visibility; + + /* control fullscreen Popup */ + GtkWidget *control_popup; + GtkWidget *fs_seek; + GtkAdjustment *fs_seekadj; + GtkWidget *fs_volume; + GtkAdjustment *fs_voladj; + GtkWidget *tcw_time_label; + + guint popup_timeout; + gboolean popup_in_progress; + GdkRectangle fullscreen_rect; + + TotemScrsaver *scr; + + /* recent file stuff */ + GtkRecentManager *recent_manager; + GtkActionGroup *recent_action_group; + guint recent_ui_id; + + /* Monitor for playlist unmounts and drives/volumes monitoring */ + GnomeVFSVolumeMonitor *monitor; + gboolean drives_changed; + + /* session */ + const char *argv0; + gint64 seek_to; + guint index; + gboolean session_restored; + + /* Window State */ + int window_w, window_h; + gboolean maximised; + + /* other */ + char *mrl; + TotemPlaylist *playlist; + GConfClient *gc; + TotemRemote *remote; + BaconMessageConnection *conn; + TotemStates state; + gboolean cursor_shown; +}; + +GtkWidget *totem_volume_create (void); + +#define SEEK_FORWARD_OFFSET 60 +#define SEEK_BACKWARD_OFFSET -15 + +#define VOLUME_DOWN_OFFSET -8 +#define VOLUME_UP_OFFSET 8 + +#define ZOOM_IN_OFFSET 1 +#define ZOOM_OUT_OFFSET -1 + +void totem_action_open (Totem *totem); +void totem_action_open_location (Totem *totem); +void totem_action_eject (Totem *totem); +void totem_action_take_screenshot (Totem *totem); +void totem_action_zoom_relative (Totem *totem, int off_pct); +void totem_action_zoom_reset (Totem *totem); +void totem_action_show_help (Totem *totem); +void totem_action_skip_to (Totem *totem); +void totem_action_show_properties (Totem *totem); + +void show_controls (Totem *totem, gboolean was_fullscreen); + +gboolean totem_is_fullscreen (Totem *totem); + + + +#endif /* __TOTEM_PRIVATE_H__ */ diff --git a/trunk/src/totem-properties-main.c b/trunk/src/totem-properties-main.c new file mode 100644 index 000000000..cb667dddd --- /dev/null +++ b/trunk/src/totem-properties-main.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2000, 2001 Eazel Inc. + * Copyright (C) 2003 Andrew Sobala <aes@gnome.org> + * Copyright (C) 2005 Bastien Nocera <hadess@hadess.net> + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> +#include <string.h> +#include <glib/gi18n-lib.h> +#include "totem-properties-view.h" +#include "bacon-video-widget.h" +#include <libnautilus-extension/nautilus-extension-types.h> +#include <libnautilus-extension/nautilus-property-page-provider.h> + +#include "totem-mime-types.h" + +static GType tpp_type = 0; +static gboolean backend_inited = FALSE; +static void property_page_provider_iface_init + (NautilusPropertyPageProviderIface *iface); +static GList *totem_properties_get_pages + (NautilusPropertyPageProvider *provider, GList *files); + +static void +totem_properties_plugin_register_type (GTypeModule *module) +{ + static const GTypeInfo info = { + sizeof (GObjectClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) NULL, + NULL, + NULL, + sizeof (GObject), + 0, + (GInstanceInitFunc) NULL + }; + static const GInterfaceInfo property_page_provider_iface_info = { + (GInterfaceInitFunc)property_page_provider_iface_init, + NULL, + NULL + }; + + tpp_type = g_type_module_register_type (module, G_TYPE_OBJECT, + "TotemPropertiesPlugin", + &info, 0); + g_type_module_add_interface (module, + tpp_type, + NAUTILUS_TYPE_PROPERTY_PAGE_PROVIDER, + &property_page_provider_iface_info); +} + +static void +property_page_provider_iface_init (NautilusPropertyPageProviderIface *iface) +{ + iface->get_pages = totem_properties_get_pages; +} + +static GList * +totem_properties_get_pages (NautilusPropertyPageProvider *provider, + GList *files) +{ + GList *pages = NULL; + NautilusFileInfo *file; + char *uri = NULL; + GtkWidget *page, *label; + NautilusPropertyPage *property_page; + guint i; + gboolean found = FALSE; + + /* only add properties page if a single file is selected */ + if (files == NULL || files->next != NULL) + goto end; + file = files->data; + + /* only add the properties page to these mime types */ + for (i = 0; i < G_N_ELEMENTS (mime_types); i++) + { + if (nautilus_file_info_is_mime_type (file, mime_types[i])) + { + found = TRUE; + break; + } + } + if (found == FALSE) + goto end; + + /* okay, make the page, init'ing the backend first if necessary */ + if (backend_inited == FALSE) { + bacon_video_widget_init_backend (NULL, NULL); + backend_inited = TRUE; + } + uri = nautilus_file_info_get_uri (file); + label = gtk_label_new (_("Audio/Video")); + page = totem_properties_view_new (uri, label); + gtk_container_set_border_width (GTK_CONTAINER (page), 6); + property_page = nautilus_property_page_new ("video-properties", + label, page); + + pages = g_list_prepend (pages, property_page); + +end: + g_free (uri); + return pages; +} + +/* --- extension interface --- */ +void +nautilus_module_initialize (GTypeModule *module) +{ + /* set up translation catalog */ + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + totem_properties_plugin_register_type (module); + totem_properties_view_register_type (module); +} + +void +nautilus_module_shutdown (void) +{ +} + +void +nautilus_module_list_types (const GType **types, + int *num_types) +{ + static GType type_list[1]; + + type_list[0] = tpp_type; + *types = type_list; + *num_types = G_N_ELEMENTS (type_list); +} + diff --git a/trunk/src/totem-properties-view.c b/trunk/src/totem-properties-view.c new file mode 100644 index 000000000..a81888170 --- /dev/null +++ b/trunk/src/totem-properties-view.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2003 Andrew Sobala <aes@gnome.org> + * Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This library 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 priv. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> + +#include "totem-properties-view.h" + +#include "bacon-video-widget-properties.h" +#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> + +struct TotemPropertiesViewPriv { + GtkWidget *label; + GtkWidget *vbox; + BaconVideoWidgetProperties *props; + BaconVideoWidget *bvw; +}; + +static GObjectClass *parent_class = NULL; +static void totem_properties_view_init (TotemPropertiesView *self); +static void totem_properties_view_class_init (TotemPropertiesViewClass *class); +static void totem_properties_view_finalize (GObject *object); + +G_DEFINE_TYPE (TotemPropertiesView, totem_properties_view, GTK_TYPE_TABLE) + +void +totem_properties_view_register_type (GTypeModule *module) +{ + totem_properties_view_get_type (); +} + +static void +totem_properties_view_class_init (TotemPropertiesViewClass *class) +{ + parent_class = g_type_class_peek_parent (class); + G_OBJECT_CLASS (class)->finalize = totem_properties_view_finalize; +} + +static void +on_got_metadata_event (BaconVideoWidget *bvw, TotemPropertiesView *props) +{ + GValue value = { 0, }; + gboolean has_audio, has_video; + const char *label = NULL; + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_HAS_VIDEO, &value); + has_video = g_value_get_boolean (&value); + g_value_unset (&value); + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_HAS_AUDIO, &value); + has_audio = g_value_get_boolean (&value); + g_value_unset (&value); + + if (has_audio == FALSE) { + if (has_video == FALSE) { + //FIXME this should be setting an error? + label = N_("Audio/Video"); + } else { + label = N_("Video"); + } + } else { + if (has_video == FALSE) { + label = N_("Audio"); + } else { + label = N_("Audio/Video"); + } + } + + gtk_label_set_text (GTK_LABEL (props->priv->label), _(label)); + + bacon_video_widget_properties_update + (props->priv->props, props->priv->bvw); +} + +static void +totem_properties_view_init (TotemPropertiesView *props) +{ + GError *err = NULL; + + props->priv = g_new0 (TotemPropertiesViewPriv, 1); + + props->priv->bvw = BACON_VIDEO_WIDGET (bacon_video_widget_new + (-1, -1, BVW_USE_TYPE_METADATA, &err)); + + if (props->priv->bvw != NULL) + { + /* Reference it, so that it's not floating */ + g_object_ref (props->priv->bvw); + + g_signal_connect (G_OBJECT (props->priv->bvw), + "got-metadata", + G_CALLBACK (on_got_metadata_event), + props); + } else { + g_warning ("Error: %s", err ? err->message : "bla"); + } + + props->priv->vbox = bacon_video_widget_properties_new (); + gtk_table_resize (GTK_TABLE (props), 1, 1); + gtk_container_add (GTK_CONTAINER (props), props->priv->vbox); + gtk_widget_show (GTK_WIDGET (props)); + + props->priv->props = BACON_VIDEO_WIDGET_PROPERTIES (props->priv->vbox); +} + +static void +totem_properties_view_finalize (GObject *object) +{ + TotemPropertiesView *props; + + props = TOTEM_PROPERTIES_VIEW (object); + + if (props->priv != NULL) + { + if (props->priv->bvw != NULL) + g_object_unref (G_OBJECT (props->priv->bvw)); + if (props->priv->label != NULL) + g_object_unref (G_OBJECT (props->priv->label)); + props->priv->bvw = NULL; + props->priv->label = NULL; + g_free (props->priv); + } + props->priv = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GtkWidget * +totem_properties_view_new (const char *location, GtkWidget *label) +{ + TotemPropertiesView *self; + + self = g_object_new (TOTEM_TYPE_PROPERTIES_VIEW, NULL); + g_object_ref (label); + self->priv->label = label; + totem_properties_view_set_location (self, location); + + return GTK_WIDGET (self); +} + +void +totem_properties_view_set_location (TotemPropertiesView *props, + const char *location) +{ + g_assert (TOTEM_IS_PROPERTIES_VIEW (props)); + + if (location != NULL && props->priv->bvw != NULL) { + GError *error = NULL; + + bacon_video_widget_close (props->priv->bvw); + bacon_video_widget_properties_reset (props->priv->props); + + if (bacon_video_widget_open (props->priv->bvw, location, &error) == FALSE) { + g_warning ("Couldn't open %s: %s", location, error->message); + g_error_free (error); + return; + } + + if (bacon_video_widget_play (props->priv->bvw, &error) == FALSE) { + g_warning ("Couldn't play %s: %s", location, error->message); + g_error_free (error); + bacon_video_widget_close (props->priv->bvw); + return; + } + + bacon_video_widget_close (props->priv->bvw); + } else { + bacon_video_widget_close (props->priv->bvw); + bacon_video_widget_properties_reset (props->priv->props); + } +} + diff --git a/trunk/src/totem-properties-view.h b/trunk/src/totem-properties-view.h new file mode 100644 index 000000000..cc061c435 --- /dev/null +++ b/trunk/src/totem-properties-view.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003 Andrew Sobala <aes@gnome.org> + * Copyright (C) 2005 Bastien Nocera <hadess@hadess.net> + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This library 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 priv. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef TOTEM_PROPERTIES_VIEW_H +#define TOTEM_PROPERTIES_VIEW_H + +#include <gtk/gtk.h> + +#define TOTEM_TYPE_PROPERTIES_VIEW (totem_properties_view_get_type ()) +#define TOTEM_PROPERTIES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_PROPERTIES_VIEW, TotemPropertiesView)) +#define TOTEM_PROPERTIES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_PROPERTIES_VIEW, TotemPropertiesViewClass)) +#define TOTEM_IS_PROPERTIES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_PROPERTIES_VIEW)) +#define TOTEM_IS_PROPERTIES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_PROPERTIES_VIEW)) + +typedef struct TotemPropertiesViewPriv TotemPropertiesViewPriv; + +typedef struct { + GtkTable parent; + TotemPropertiesViewPriv *priv; +} TotemPropertiesView; + +typedef struct { + GtkTableClass parent; +} TotemPropertiesViewClass; + +GType totem_properties_view_get_type (void); +void totem_properties_view_register_type (GTypeModule *module); + +GtkWidget *totem_properties_view_new (const char *location, + GtkWidget *label); +void totem_properties_view_set_location (TotemPropertiesView *view, + const char *location); + +#endif /* TOTEM_PROPERTIES_VIEW_H */ diff --git a/trunk/src/totem-remote.c b/trunk/src/totem-remote.c new file mode 100644 index 000000000..074b1a837 --- /dev/null +++ b/trunk/src/totem-remote.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2002 James Willcox <jwillcox@gnome.org> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + + +#include <config.h> +#include <glib.h> +#include <string.h> + +#include "totem-remote.h" + +#ifdef HAVE_REMOTE + +/* Media player keys support */ +#ifdef HAVE_MEDIA_PLAYER_KEYS +#include <gnome-settings-daemon/gnome-settings-client.h> +#include "totem-marshal.h" +#endif /*HAVE_MEDIA_PLAYER_KEYS */ + +#ifdef HAVE_LIRC +#include <stdio.h> +#include <lirc/lirc_client.h> +#include <gconf/gconf-client.h> + +/* strings that we recognize as commands from lirc */ +#define TOTEM_IR_COMMAND_PLAY "play" +#define TOTEM_IR_COMMAND_PAUSE "pause" +#define TOTEM_IR_COMMAND_NEXT "next" +#define TOTEM_IR_COMMAND_PREVIOUS "previous" +#define TOTEM_IR_COMMAND_SEEK_FORWARD "seek_forward" +#define TOTEM_IR_COMMAND_SEEK_BACKWARD "seek_backward" +#define TOTEM_IR_COMMAND_VOLUME_UP "volume_up" +#define TOTEM_IR_COMMAND_VOLUME_DOWN "volume_down" +#define TOTEM_IR_COMMAND_FULLSCREEN "fullscreen" +#define TOTEM_IR_COMMAND_QUIT "quit" +#define TOTEM_IR_COMMAND_UP "up" +#define TOTEM_IR_COMMAND_DOWN "down" +#define TOTEM_IR_COMMAND_LEFT "left" +#define TOTEM_IR_COMMAND_RIGHT "right" +#define TOTEM_IR_COMMAND_SELECT "select" +#define TOTEM_IR_COMMAND_MENU "menu" +#define TOTEM_IR_COMMAND_PLAYPAUSE "play_pause" +#define TOTEM_IR_COMMAND_ZOOM_UP "zoom_up" +#define TOTEM_IR_COMMAND_ZOOM_DOWN "zoom_down" +#define TOTEM_IR_COMMAND_SHOW_PLAYING "show_playing" +#define TOTEM_IR_COMMAND_EJECT "eject" +#define TOTEM_IR_COMMAND_PLAY_DVD "play_dvd" +#define TOTEM_IR_COMMAND_MUTE "mute" +#endif /* HAVE_LIRC */ + +struct _TotemRemote { + GObject parent; + + /* Media player keys suppport */ +#ifdef HAVE_MEDIA_PLAYER_KEYS + DBusGProxy *media_player_keys_proxy; +#endif +}; + +enum +{ + BUTTON_PRESSED, + LAST_SIGNAL +}; + +static guint totem_remote_signals[LAST_SIGNAL] = { 0 }; +#ifdef HAVE_LIRC +static GIOChannel *lirc_channel = NULL; +static GList *listeners = NULL; +#endif /* HAVE_LIRC */ + + +G_DEFINE_TYPE(TotemRemote, totem_remote, G_TYPE_OBJECT) + +#ifdef HAVE_LIRC +static TotemRemoteCommand +totem_lirc_to_command (const gchar *str) +{ + if (strcmp (str, TOTEM_IR_COMMAND_PLAY) == 0) + return TOTEM_REMOTE_COMMAND_PLAY; + else if (strcmp (str, TOTEM_IR_COMMAND_PAUSE) == 0) + return TOTEM_REMOTE_COMMAND_PAUSE; + else if (strcmp (str, TOTEM_IR_COMMAND_PLAYPAUSE) == 0) + return TOTEM_REMOTE_COMMAND_PLAYPAUSE; + else if (strcmp (str, TOTEM_IR_COMMAND_NEXT) == 0) + return TOTEM_REMOTE_COMMAND_NEXT; + else if (strcmp (str, TOTEM_IR_COMMAND_PREVIOUS) == 0) + return TOTEM_REMOTE_COMMAND_PREVIOUS; + else if (strcmp (str, TOTEM_IR_COMMAND_SEEK_FORWARD) == 0) + return TOTEM_REMOTE_COMMAND_SEEK_FORWARD; + else if (strcmp (str, TOTEM_IR_COMMAND_SEEK_BACKWARD) == 0) + return TOTEM_REMOTE_COMMAND_SEEK_BACKWARD; + else if (strcmp (str, TOTEM_IR_COMMAND_VOLUME_UP) == 0) + return TOTEM_REMOTE_COMMAND_VOLUME_UP; + else if (strcmp (str, TOTEM_IR_COMMAND_VOLUME_DOWN) == 0) + return TOTEM_REMOTE_COMMAND_VOLUME_DOWN; + else if (strcmp (str, TOTEM_IR_COMMAND_FULLSCREEN) == 0) + return TOTEM_REMOTE_COMMAND_FULLSCREEN; + else if (strcmp (str, TOTEM_IR_COMMAND_QUIT) == 0) + return TOTEM_REMOTE_COMMAND_QUIT; + else if (strcmp (str, TOTEM_IR_COMMAND_UP) == 0) + return TOTEM_REMOTE_COMMAND_UP; + else if (strcmp (str, TOTEM_IR_COMMAND_DOWN) == 0) + return TOTEM_REMOTE_COMMAND_DOWN; + else if (strcmp (str, TOTEM_IR_COMMAND_LEFT) == 0) + return TOTEM_REMOTE_COMMAND_LEFT; + else if (strcmp (str, TOTEM_IR_COMMAND_RIGHT) == 0) + return TOTEM_REMOTE_COMMAND_RIGHT; + else if (strcmp (str, TOTEM_IR_COMMAND_SELECT) == 0) + return TOTEM_REMOTE_COMMAND_SELECT; + else if (strcmp (str, TOTEM_IR_COMMAND_MENU) == 0) + return TOTEM_REMOTE_COMMAND_DVD_MENU; + else if (strcmp (str, TOTEM_IR_COMMAND_ZOOM_UP) == 0) + return TOTEM_REMOTE_COMMAND_ZOOM_UP; + else if (strcmp (str, TOTEM_IR_COMMAND_ZOOM_DOWN) == 0) + return TOTEM_REMOTE_COMMAND_ZOOM_DOWN; + else if (strcmp (str, TOTEM_IR_COMMAND_SHOW_PLAYING) == 0) + return TOTEM_REMOTE_COMMAND_SHOW_PLAYING; + else if (strcmp (str, TOTEM_IR_COMMAND_EJECT) == 0) + return TOTEM_REMOTE_COMMAND_EJECT; + else if (strcmp (str, TOTEM_IR_COMMAND_PLAY_DVD) == 0) + return TOTEM_REMOTE_COMMAND_PLAY_DVD; + else if (strcmp (str, TOTEM_IR_COMMAND_MUTE) == 0) + return TOTEM_REMOTE_COMMAND_MUTE; + else + return TOTEM_REMOTE_COMMAND_UNKNOWN; +} + +static struct lirc_config *config; + +static gboolean +totem_remote_read_code (GIOChannel *source, GIOCondition condition, + gpointer user_data) +{ + char *code; + char *str = NULL; + GList *tmp; + TotemRemoteCommand cmd; + + /* this _could_ block, but it shouldn't */ + lirc_nextcode (&code); + + if (code == NULL) { + /* the code was incomplete or something */ + return TRUE; + } + + if (lirc_code2char (config, code, &str) != 0) { + g_message ("Couldn't convert lirc code to string."); + return TRUE; + } + + if (str == NULL) { + /* there was no command associated with the code */ + g_free (code); + return TRUE; + } + + cmd = totem_lirc_to_command (str); + + tmp = listeners; + while (tmp) { + TotemRemote *remote = tmp->data; + + g_signal_emit (remote, totem_remote_signals[BUTTON_PRESSED], 0, + cmd); + + tmp = tmp->next; + } + + g_free (code); + + /* this causes a crash, so I guess I'm not supposed to free it? + * g_free (str); + */ + + return TRUE; +} +#endif /* HAVE_LIRC */ + +#ifdef HAVE_MEDIA_PLAYER_KEYS +static void +on_media_player_key_pressed (DBusGProxy *proxy, const gchar *application, const gchar *key, TotemRemote *remote) +{ + if (strcmp ("Totem", application) == 0) { + if (strcmp ("Play", key) == 0) + g_signal_emit (remote, totem_remote_signals[BUTTON_PRESSED], 0, TOTEM_REMOTE_COMMAND_PLAYPAUSE); + else if (strcmp ("Previous", key) == 0) + g_signal_emit (remote, totem_remote_signals[BUTTON_PRESSED], 0, TOTEM_REMOTE_COMMAND_PREVIOUS); + else if (strcmp ("Next", key) == 0) + g_signal_emit (remote, totem_remote_signals[BUTTON_PRESSED], 0, TOTEM_REMOTE_COMMAND_NEXT); + } +} +#endif /* HAVE_MEDIA_PLAYER_KEYS */ + +static void +totem_remote_finalize (GObject *object) +{ + GError *error = NULL; + TotemRemote *remote; + + g_return_if_fail (object != NULL); + g_return_if_fail (TOTEM_IS_REMOTE (object)); + + remote = TOTEM_REMOTE (object); + +#ifdef HAVE_LIRC + lirc_freeconfig (config); + g_free(config); + + listeners = g_list_remove (listeners, remote); + + if (listeners == NULL && lirc_channel != NULL) { + g_io_channel_shutdown (lirc_channel, FALSE, &error); + if (error != NULL) { + g_warning ("Couldn't destroy lirc connection: %s", + error->message); + g_error_free (error); + } + + lirc_deinit (); + } +#endif /* HAVE_LIRC */ + +#ifdef HAVE_MEDIA_PLAYER_KEYS + if (remote->media_player_keys_proxy != NULL) + org_gnome_SettingsDaemon_release_media_player_keys (remote->media_player_keys_proxy, "Totem", NULL); +#endif /* HAVE_MEDIA_PLAYER_KEYS */ +} + +static void +totem_remote_class_init (TotemRemoteClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = totem_remote_finalize; + + totem_remote_signals[BUTTON_PRESSED] = + g_signal_new ("button_pressed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemRemoteClass, button_pressed), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); +} + + +static void +totem_remote_init (TotemRemote *remote) +{ +#ifdef HAVE_MEDIA_PLAYER_KEYS + DBusGConnection *bus; + GError *error = NULL; +#endif /* HAVE_MEDIA_PLAYER_KEYS */ +#ifdef HAVE_LIRC + int fd; +#endif /* HAVE_LIRC */ + +#ifdef HAVE_MEDIA_PLAYER_KEYS + bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (bus == NULL) { + g_warning ("Error connecting to DBus: %s", error->message); + } else { + remote->media_player_keys_proxy = dbus_g_proxy_new_for_name (bus, + "org.gnome.SettingsDaemon", "/org/gnome/SettingsDaemon", + "org.gnome.SettingsDaemon"); + + org_gnome_SettingsDaemon_grab_media_player_keys (remote->media_player_keys_proxy, + "Totem", 0, NULL); + + dbus_g_object_register_marshaller (totem_marshal_VOID__STRING_STRING, + G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); + + dbus_g_proxy_add_signal (remote->media_player_keys_proxy, "MediaPlayerKeyPressed", + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); + + dbus_g_proxy_connect_signal (remote->media_player_keys_proxy, "MediaPlayerKeyPressed", + G_CALLBACK (on_media_player_key_pressed), remote, NULL); + } +#endif + +#ifdef HAVE_LIRC + if (lirc_channel == NULL) { + fd = lirc_init ("Totem", 0); + + if (fd < 0) { + GConfClient *gc; + + gc = gconf_client_get_default (); + if (gc == NULL) + return; + + if (gconf_client_get_bool (gc, GCONF_PREFIX"/debug", NULL) != FALSE) + g_message ("Couldn't initialize lirc.\n"); + g_object_unref (gc); + return; + } + + config = g_new(struct lirc_config, 1); + + if (lirc_readconfig (NULL, &config, NULL) != 0) { + g_message ("Couldn't read lirc config."); + return; + } + + lirc_channel = g_io_channel_unix_new (fd); + + g_io_add_watch (lirc_channel, G_IO_IN, + (GIOFunc) totem_remote_read_code, NULL); + } + + listeners = g_list_prepend (listeners, remote); +#endif /* HAVE_LIRC */ + +} + +TotemRemote * +totem_remote_new (void) +{ + return g_object_new (TOTEM_TYPE_REMOTE, NULL); +} + + +#ifdef HAVE_MEDIA_PLAYER_KEYS +void +totem_remote_window_activated (TotemRemote *remote) +{ + + g_return_if_fail (TOTEM_IS_REMOTE (remote)); + g_return_if_fail (DBUS_IS_G_PROXY (remote->media_player_keys_proxy)); + + org_gnome_SettingsDaemon_grab_media_player_keys (remote->media_player_keys_proxy, + "Totem", 0, NULL); +} +#endif /* HAVE_MEDIA_PLAYER_KEYS */ + +#endif /* HAVE_REMOTE */ diff --git a/trunk/src/totem-remote.h b/trunk/src/totem-remote.h new file mode 100644 index 000000000..79ec1859d --- /dev/null +++ b/trunk/src/totem-remote.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2002 James Willcox <jwillcox@gnome.org> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <glib.h> +#include <glib-object.h> + +#ifndef __TOTEM_REMOTE_H +#define __TOTEM_REMOTE_H + +G_BEGIN_DECLS + +#define TOTEM_TYPE_REMOTE (totem_remote_get_type ()) +#define TOTEM_REMOTE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_REMOTE, TotemRemote)) +#define TOTEM_REMOTE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_REMOTE, TotemRemoteClass)) +#define TOTEM_IS_REMOTE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_REMOTE)) +#define TOTEM_IS_REMOTE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_REMOTE)) +#define TOTEM_REMOTE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_REMOTE, TotemRemoteClass)) + +typedef struct _TotemRemote TotemRemote; + +typedef enum { + TOTEM_REMOTE_COMMAND_UNKNOWN, + TOTEM_REMOTE_COMMAND_PLAY, + TOTEM_REMOTE_COMMAND_PAUSE, + TOTEM_REMOTE_COMMAND_PLAYPAUSE, + TOTEM_REMOTE_COMMAND_NEXT, + TOTEM_REMOTE_COMMAND_PREVIOUS, + TOTEM_REMOTE_COMMAND_SEEK_FORWARD, + TOTEM_REMOTE_COMMAND_SEEK_BACKWARD, + TOTEM_REMOTE_COMMAND_VOLUME_UP, + TOTEM_REMOTE_COMMAND_VOLUME_DOWN, + TOTEM_REMOTE_COMMAND_FULLSCREEN, + TOTEM_REMOTE_COMMAND_QUIT, + TOTEM_REMOTE_COMMAND_ENQUEUE, + TOTEM_REMOTE_COMMAND_REPLACE, + TOTEM_REMOTE_COMMAND_SHOW, + TOTEM_REMOTE_COMMAND_TOGGLE_CONTROLS, + TOTEM_REMOTE_COMMAND_SHOW_PLAYING, + TOTEM_REMOTE_COMMAND_UP, + TOTEM_REMOTE_COMMAND_DOWN, + TOTEM_REMOTE_COMMAND_LEFT, + TOTEM_REMOTE_COMMAND_RIGHT, + TOTEM_REMOTE_COMMAND_SELECT, + TOTEM_REMOTE_COMMAND_DVD_MENU, + TOTEM_REMOTE_COMMAND_ZOOM_UP, + TOTEM_REMOTE_COMMAND_ZOOM_DOWN, + TOTEM_REMOTE_COMMAND_EJECT, + TOTEM_REMOTE_COMMAND_PLAY_DVD, + TOTEM_REMOTE_COMMAND_MUTE +} TotemRemoteCommand; + +#define SHOW_PLAYING_NO_TRACKS "NONE" + +typedef struct +{ + GObjectClass parent_class; + + void (* button_pressed) (TotemRemote *remote, TotemRemoteCommand cmd); +} TotemRemoteClass; + +GType totem_remote_get_type (void); + +TotemRemote *totem_remote_new (void); + +#ifdef HAVE_MEDIA_PLAYER_KEYS +void totem_remote_window_activated (TotemRemote *remote); +#endif /* HAVE_MEDIA_PLAYER_KEYS */ + +G_END_DECLS + +#endif /* __TOTEM_REMOTE_H */ diff --git a/trunk/src/totem-screenshot.c b/trunk/src/totem-screenshot.c new file mode 100644 index 000000000..f003a6669 --- /dev/null +++ b/trunk/src/totem-screenshot.c @@ -0,0 +1,424 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* totem-screenshot.c + + Copyright (C) 2004 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" +#include "totem-screenshot.h" + +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <gconf/gconf-client.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "debug.h" + +struct TotemScreenshotPrivate +{ + GladeXML *xml; + GdkPixbuf *pixbuf, *scaled; + char *temp_file; +}; + +static const GtkTargetEntry target_table[] = { + { "text/uri-list", 0, 0 }, +}; + +static GtkTargetEntry source_table[] = { + { "text/uri-list", 0, 0 }, +}; + +static void totem_screenshot_class_init (TotemScreenshotClass *class); +static void totem_screenshot_init (TotemScreenshot *screenshot); + +G_DEFINE_TYPE(TotemScreenshot, totem_screenshot, GTK_TYPE_DIALOG) + +static void +totem_screenshot_action_error (char *title, char *reason, + TotemScreenshot *screenshot) +{ + GtkWidget *error_dialog; + + error_dialog = + gtk_message_dialog_new (GTK_WINDOW (screenshot), + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + title); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), reason); + + gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5); + gtk_dialog_set_default_response (GTK_DIALOG (error_dialog), + GTK_RESPONSE_OK); + g_signal_connect (G_OBJECT (error_dialog), "destroy", G_CALLBACK + (gtk_widget_destroy), error_dialog); + g_signal_connect (G_OBJECT (error_dialog), "response", G_CALLBACK + (gtk_widget_destroy), error_dialog); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + + gtk_widget_show (error_dialog); +} + +static char * +screenshot_make_filename_helper (char *filename, gboolean desktop_exists) +{ + gboolean home_as_desktop; + GConfClient *gc; + + gc = gconf_client_get_default (); + home_as_desktop = gconf_client_get_bool (gc, + "/apps/nautilus/preferences/desktop_is_home_dir", + NULL); + g_object_unref (G_OBJECT (gc)); + + if (desktop_exists != FALSE && home_as_desktop == FALSE) + { + char *fullpath; + + fullpath = g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), + "Desktop", NULL); + desktop_exists = g_file_test (fullpath, G_FILE_TEST_EXISTS); + g_free (fullpath); + + if (desktop_exists != FALSE) + { + return g_build_filename (g_get_home_dir (), + "Desktop", filename, NULL); + } else { + return g_build_filename (g_get_home_dir (), + ".gnome-desktop", filename, NULL); + } + } else { + return g_build_filename (g_get_home_dir (), filename, NULL); + } +} + +static char * +screenshot_make_filename (TotemScreenshot *screenshot) +{ + GtkWidget *radiobutton, *entry; + gboolean on_desktop; + char *fullpath, *filename; + int i = 0; + gboolean desktop_exists; + + radiobutton = glade_xml_get_widget (screenshot->_priv->xml, + "tsw_save2desk_radiobutton"); + on_desktop = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON + (radiobutton)); + + /* Test if we have a desktop directory */ + fullpath = g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), + "Desktop", NULL); + desktop_exists = g_file_test (fullpath, G_FILE_TEST_EXISTS); + g_free (fullpath); + if (desktop_exists == FALSE) + { + fullpath = g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), + ".gnome-desktop", NULL); + desktop_exists = g_file_test (fullpath, G_FILE_TEST_EXISTS); + g_free (fullpath); + } + + if (on_desktop != FALSE) + { + filename = g_strdup_printf (_("Screenshot%d.png"), i); + fullpath = screenshot_make_filename_helper (filename, + desktop_exists); + + while (g_file_test (fullpath, G_FILE_TEST_EXISTS) != FALSE + && i < G_MAXINT) + { + i++; + g_free (filename); + g_free (fullpath); + + filename = g_strdup_printf (_("Screenshot%d.png"), i); + fullpath = screenshot_make_filename_helper (filename, + desktop_exists); + } + + g_free (filename); + } else { + entry = glade_xml_get_widget (screenshot->_priv->xml, "tsw_save2file_combo_entry"); + if (gtk_entry_get_text (GTK_ENTRY (entry)) == NULL) + return NULL; + + fullpath = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + } + + return fullpath; +} + +static void +on_radiobutton_shot_toggled (GtkToggleButton *togglebutton, + TotemScreenshot *screenshot) +{ + GtkWidget *radiobutton, *entry; + + radiobutton = glade_xml_get_widget (screenshot->_priv->xml, "tsw_save2file_radiobutton"); + entry = glade_xml_get_widget (screenshot->_priv->xml, "tsw_save2file_fileentry"); + gtk_widget_set_sensitive (entry, gtk_toggle_button_get_active + (GTK_TOGGLE_BUTTON (radiobutton))); +} + +static void +totem_screenshot_response (TotemScreenshot *screenshot, int response) +{ + char *filename; + GError *err = NULL; + + if (response == GTK_RESPONSE_OK) + { + filename = screenshot_make_filename (screenshot); + if (g_file_test (filename, G_FILE_TEST_EXISTS) != FALSE) + { + char *msg; + + msg = g_strdup_printf (_("File '%s' already exists."), filename); + totem_screenshot_action_error (msg, + _("The screenshot was not saved"), + screenshot); + g_free (msg); + g_free (filename); + return; + } + + if (gdk_pixbuf_save (screenshot->_priv->pixbuf, + filename, "png", &err, NULL) == FALSE) + { + totem_screenshot_action_error + (_("There was an error saving the screenshot."), + err->message, screenshot); + g_error_free (err); + } + + g_free (filename); + } +} + +static void +totem_screenshot_init (TotemScreenshot *screenshot) +{ + screenshot->_priv = g_new0 (TotemScreenshotPrivate, 1); + + gtk_container_set_border_width (GTK_CONTAINER (screenshot), 5); +} + +static void +totem_screenshot_temp_file (TotemScreenshot *screenshot, gboolean create) +{ + if (create) + { + char *dir, *fulldir; + + dir = g_strdup_printf ("totem-screenshot-%d", getpid ()); + fulldir = g_build_filename (g_get_tmp_dir (), dir, NULL); + if (g_mkdir (fulldir, 0700) < 0) { + g_free (fulldir); + g_free (dir); + return; + } + screenshot->_priv->temp_file = g_build_filename + (g_get_tmp_dir (), + dir, _("Screenshot.png"), NULL); + } else { + char *dirname; + + if (screenshot->_priv->temp_file == NULL) + return; + + unlink (screenshot->_priv->temp_file); + dirname = g_path_get_dirname (screenshot->_priv->temp_file); + rmdir (dirname); + g_free (dirname); + + g_free (screenshot->_priv->temp_file); + } +} + +static void +totem_screenshot_finalize (GObject *object) +{ + TotemScreenshot *screenshot = TOTEM_SCREENSHOT (object); + + g_return_if_fail (object != NULL); + + totem_screenshot_temp_file (screenshot, FALSE); + + if (screenshot->_priv->pixbuf != NULL) + g_object_unref (screenshot->_priv->pixbuf); + if (screenshot->_priv->scaled != NULL) + g_object_unref (screenshot->_priv->scaled); + + G_OBJECT_CLASS (totem_screenshot_parent_class)->finalize (object); +} + +static void +drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time, + TotemScreenshot *screenshot) +{ + char *string; + + /* FIXME We should cancel the drag */ + if (screenshot->_priv->temp_file == NULL) + return; + + string = g_strdup_printf ("file://%s\r\n", + screenshot->_priv->temp_file); + gtk_selection_data_set (selection_data, + selection_data->target, + 8, (guchar *)string, strlen (string)+1); + g_free (string); +} + +static void +drag_begin (GtkWidget *widget, GdkDragContext *context, + TotemScreenshot *screenshot) +{ + if (screenshot->_priv->temp_file == NULL) + { + gtk_drag_set_icon_pixbuf (context, screenshot->_priv->scaled, + 0, 0); + totem_screenshot_temp_file (screenshot, TRUE); + g_return_if_fail (screenshot->_priv->temp_file != NULL); + gdk_pixbuf_save (screenshot->_priv->pixbuf, + screenshot->_priv->temp_file, "png", + NULL, NULL); + } +} + +GtkWidget* +totem_screenshot_new (const char *glade_filename, GdkPixbuf *screen_image) +{ + TotemScreenshot *screenshot; + GtkWidget *container, *item; + char *filename; + GtkWidget *dialog, *image, *entry; + int width, height; + + g_return_val_if_fail (glade_filename != NULL, NULL); + + screenshot = TOTEM_SCREENSHOT (g_object_new (GTK_TYPE_SCREENSHOT, NULL)); + + screenshot->_priv->xml = glade_xml_new (glade_filename, "vbox11", NULL); + if (screenshot->_priv->xml == NULL) + { + totem_screenshot_finalize (G_OBJECT (screenshot)); + return NULL; + } + + screenshot->_priv->pixbuf = screen_image; + g_object_ref (screenshot->_priv->pixbuf); + + gtk_window_set_title (GTK_WINDOW (screenshot), _("Save Screenshot")); + gtk_dialog_set_has_separator (GTK_DIALOG (screenshot), FALSE); + gtk_dialog_add_buttons (GTK_DIALOG (screenshot), + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + GTK_STOCK_SAVE, GTK_RESPONSE_OK, + NULL); + g_signal_connect (G_OBJECT (screenshot), "response", + G_CALLBACK (totem_screenshot_response), + screenshot); + /* Screenshot dialog */ + item = glade_xml_get_widget (screenshot->_priv->xml, + "totem_screenshot_window"); + //FIXME +// g_signal_connect (G_OBJECT (item), "delete-event", +// G_CALLBACK (hide_screenshot), totem); + item = glade_xml_get_widget (screenshot->_priv->xml, + "tsw_save2file_radiobutton"); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_radiobutton_shot_toggled), + screenshot); + item = glade_xml_get_widget (screenshot->_priv->xml, + "tsw_save2desk_radiobutton"); + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (on_radiobutton_shot_toggled), + screenshot); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item), TRUE); + + filename = screenshot_make_filename (screenshot); + height = 200; + width = height * gdk_pixbuf_get_width (screen_image) + / gdk_pixbuf_get_height (screen_image); + screenshot->_priv->scaled = gdk_pixbuf_scale_simple (screen_image, + width, height, GDK_INTERP_BILINEAR); + + dialog = glade_xml_get_widget (screenshot->_priv->xml, + "totem_screenshot_window"); + image = glade_xml_get_widget (screenshot->_priv->xml, "tsw_shot_image"); + gtk_image_set_from_pixbuf (GTK_IMAGE (image), + screenshot->_priv->scaled); + + /* Setup the DnD for the image */ + g_signal_connect (G_OBJECT (screenshot), "drag_begin", + G_CALLBACK (drag_begin), screenshot); + g_signal_connect (G_OBJECT (screenshot), "drag_data_get", + G_CALLBACK (drag_data_get), screenshot); + gtk_drag_source_set (GTK_WIDGET (screenshot), + GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + source_table, G_N_ELEMENTS (source_table), + GDK_ACTION_COPY); + + entry = glade_xml_get_widget (screenshot->_priv->xml, + "tsw_save2file_combo_entry"); + gtk_entry_set_text (GTK_ENTRY (entry), filename); + g_free (filename); + + item = glade_xml_get_widget (screenshot->_priv->xml, + "tsw_save2file_fileentry"); + { + GValue value = { 0, }; + g_value_init (&value, GTK_TYPE_FILE_CHOOSER_ACTION); + g_value_set_enum (&value, GTK_FILE_CHOOSER_ACTION_SAVE); + g_object_set_property (G_OBJECT (item), + "filechooser-action", &value); + } + + container = glade_xml_get_widget (screenshot->_priv->xml, "vbox11"); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (screenshot)->vbox), + container, + TRUE, /* expand */ + TRUE, /* fill */ + 0); /* padding */ + + gtk_widget_show_all (GTK_DIALOG (screenshot)->vbox); + + return GTK_WIDGET (screenshot); +} + +static void +totem_screenshot_class_init (TotemScreenshotClass *klass) +{ + G_OBJECT_CLASS (klass)->finalize = totem_screenshot_finalize; + //FIXME override response +} diff --git a/trunk/src/totem-screenshot.h b/trunk/src/totem-screenshot.h new file mode 100644 index 000000000..20ed0751c --- /dev/null +++ b/trunk/src/totem-screenshot.h @@ -0,0 +1,55 @@ +/* totem-screenshot.h: Simple screenshot dialog + + Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_SCREENSHOT_H +#define TOTEM_SCREENSHOT_H + +#include <gtk/gtkdialog.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_SCREENSHOT (totem_screenshot_get_type ()) +#define TOTEM_SCREENSHOT(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_SCREENSHOT, TotemScreenshot)) +#define TOTEM_SCREENSHOT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_SCREENSHOT, TotemScreenshotClass)) +#define GTK_IS_SCREENSHOT(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_SCREENSHOT)) +#define GTK_IS_SCREENSHOT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SCREENSHOT)) + +typedef struct TotemScreenshot TotemScreenshot; +typedef struct TotemScreenshotClass TotemScreenshotClass; +typedef struct TotemScreenshotPrivate TotemScreenshotPrivate; + +struct TotemScreenshot { + GtkDialog parent; + TotemScreenshotPrivate *_priv; +}; + +struct TotemScreenshotClass { + GtkDialogClass parent_class; +}; + +GtkType totem_screenshot_get_type (void); +GtkWidget *totem_screenshot_new (const char *glade_filename, + GdkPixbuf *playing_pix); + +G_END_DECLS + +#endif /* TOTEM_SCREENSHOT_H */ diff --git a/trunk/src/totem-scrsaver.c b/trunk/src/totem-scrsaver.c new file mode 100644 index 000000000..06d145a1b --- /dev/null +++ b/trunk/src/totem-scrsaver.c @@ -0,0 +1,455 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + + Copyright (C) 2004-2006 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + + +#include "config.h" + +#include <glib/gi18n.h> + +#include <gdk/gdk.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#include <X11/keysym.h> + +#ifdef HAVE_XTEST +#include <X11/extensions/XTest.h> +#endif /* HAVE_XTEST */ +#endif /* GDK_WINDOWING_X11 */ + +#ifdef WITH_DBUS +#include <dbus/dbus.h> +#include <dbus/dbus-glib.h> + +#define GS_SERVICE "org.gnome.ScreenSaver" +#define GS_PATH "/org/gnome/ScreenSaver" +#define GS_INTERFACE "org.gnome.ScreenSaver" +#endif /* WITH_DBUS */ + +#include "totem-scrsaver.h" + +#define XSCREENSAVER_MIN_TIMEOUT 60 + +static GObjectClass *parent_class = NULL; +static void totem_scrsaver_class_init (TotemScrsaverClass *class); +static void totem_scrsaver_init (TotemScrsaver *parser); +static void totem_scrsaver_finalize (GObject *object); + + +struct TotemScrsaverPrivate { + /* Whether the screensaver is disabled */ + gboolean disabled; + +#ifdef WITH_DBUS + DBusGConnection *connection; + DBusGProxy *gs_proxy; + guint32 cookie; +#endif /* WITH_DBUS */ + + /* To save the screensaver info */ + int timeout; + int interval; + int prefer_blanking; + int allow_exposures; + + /* For use with XTest */ + int keycode1, keycode2; + int *keycode; + gboolean have_xtest; +}; + +G_DEFINE_TYPE(TotemScrsaver, totem_scrsaver, G_TYPE_OBJECT) + +static gboolean +screensaver_is_running_dbus (TotemScrsaver *scr) +{ +#ifdef WITH_DBUS + if (! scr->priv->connection) + return FALSE; + + if (! scr->priv->gs_proxy) + return FALSE; + + return TRUE; +#else + return FALSE; +#endif /* WITH_DBUS */ +} + +static void +screensaver_inhibit_dbus (TotemScrsaver *scr, + gboolean inhibit) +{ +#ifdef WITH_DBUS + GError *error; + gboolean res; + + g_return_if_fail (scr != NULL); + g_return_if_fail (scr->priv->connection != NULL); + g_return_if_fail (scr->priv->gs_proxy != NULL); + + error = NULL; + if (inhibit) { + char *application; + char *reason; + guint32 cookie; + + application = g_strdup ("Totem"); + reason = g_strdup (_("Playing a movie")); + + res = dbus_g_proxy_call (scr->priv->gs_proxy, + "Inhibit", + &error, + G_TYPE_STRING, application, + G_TYPE_STRING, reason, + G_TYPE_INVALID, + G_TYPE_UINT, &cookie, + G_TYPE_INVALID); + + if (res) { + /* save the cookie */ + scr->priv->cookie = cookie; + } else { + /* try the old API */ + res = dbus_g_proxy_call (scr->priv->gs_proxy, + "InhibitActivation", + &error, + G_TYPE_STRING, reason, + G_TYPE_INVALID, + G_TYPE_INVALID); + } + + g_free (reason); + g_free (application); + + } else { + res = dbus_g_proxy_call (scr->priv->gs_proxy, + "UnInhibit", + &error, + G_TYPE_UINT, scr->priv->cookie, + G_TYPE_INVALID, + G_TYPE_INVALID); + if (res) { + /* clear the cookie */ + scr->priv->cookie = 0; + } else { + /* try the old API */ + res = dbus_g_proxy_call (scr->priv->gs_proxy, + "AllowActivation", + &error, + G_TYPE_INVALID, + G_TYPE_INVALID); + } + } + + if (! res) { + if (error) { + g_warning ("Problem inhibiting the screensaver: %s", error->message); + g_error_free (error); + } + } +#endif /* WITH_DBUS */ +} + +static void +screensaver_enable_dbus (TotemScrsaver *scr) +{ + screensaver_inhibit_dbus (scr, FALSE); +} + +static void +screensaver_disable_dbus (TotemScrsaver *scr) +{ + screensaver_inhibit_dbus (scr, TRUE); +} + +#ifdef WITH_DBUS +static void +gs_proxy_destroy_cb (GObject *proxy, + TotemScrsaver *scr) +{ + g_warning ("Detected that GNOME screensaver has left the bus"); + + /* just invalidate for now */ + scr->priv->gs_proxy = NULL; +} +#endif + +static void +screensaver_init_dbus (TotemScrsaver *scr) +{ +#ifdef WITH_DBUS + GError *error = NULL; + + scr->priv->connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + + if (! scr->priv->connection) { + if (error) { + g_warning ("Failed to connect to the session bus: %s", error->message); + g_error_free (error); + } + return; + } + + scr->priv->gs_proxy = dbus_g_proxy_new_for_name_owner (scr->priv->connection, + GS_SERVICE, + GS_PATH, + GS_INTERFACE, + NULL); + if (scr->priv->gs_proxy != NULL) { + g_signal_connect_object (scr->priv->gs_proxy, + "destroy", + G_CALLBACK (gs_proxy_destroy_cb), + scr, + 0); + + } + +#endif /* WITH_DBUS */ +} + +static void +screensaver_finalize_dbus (TotemScrsaver *scr) +{ +#ifdef WITH_DBUS + if (scr->priv->gs_proxy) { + g_object_unref (scr->priv->gs_proxy); + } +#endif /* WITH_DBUS */ +} + +#ifdef GDK_WINDOWING_X11 +static void +screensaver_enable_x11 (TotemScrsaver *scr) +{ + +#ifdef HAVE_XTEST + if (scr->priv->have_xtest != FALSE) + { + g_source_remove_by_user_data (scr); + return; + } +#endif /* HAVE_XTEST */ + + XLockDisplay (GDK_DISPLAY()); + XSetScreenSaver (GDK_DISPLAY(), + scr->priv->timeout, + scr->priv->interval, + scr->priv->prefer_blanking, + scr->priv->allow_exposures); + XUnlockDisplay (GDK_DISPLAY()); +} + +#ifdef HAVE_XTEST +static gboolean +fake_event (TotemScrsaver *scr) +{ + if (scr->priv->disabled) + { + XLockDisplay (GDK_DISPLAY()); + XTestFakeKeyEvent (GDK_DISPLAY(), *scr->priv->keycode, + True, CurrentTime); + XTestFakeKeyEvent (GDK_DISPLAY(), *scr->priv->keycode, + False, CurrentTime); + XUnlockDisplay (GDK_DISPLAY()); + /* Swap the keycode */ + if (scr->priv->keycode == &scr->priv->keycode1) + scr->priv->keycode = &scr->priv->keycode2; + else + scr->priv->keycode = &scr->priv->keycode1; + } + + return TRUE; +} +#endif /* HAVE_XTEST */ + +static void +screensaver_disable_x11 (TotemScrsaver *scr) +{ + +#ifdef HAVE_XTEST + if (scr->priv->have_xtest != FALSE) + { + XLockDisplay (GDK_DISPLAY()); + XGetScreenSaver(GDK_DISPLAY(), &scr->priv->timeout, + &scr->priv->interval, + &scr->priv->prefer_blanking, + &scr->priv->allow_exposures); + XUnlockDisplay (GDK_DISPLAY()); + + if (scr->priv->timeout != 0) + { + g_timeout_add (scr->priv->timeout / 2 * 1000, + (GSourceFunc) fake_event, scr); + } else { + g_timeout_add (XSCREENSAVER_MIN_TIMEOUT / 2 * 1000, + (GSourceFunc) fake_event, scr); + } + + return; + } +#endif /* HAVE_XTEST */ + + XLockDisplay (GDK_DISPLAY()); + XGetScreenSaver(GDK_DISPLAY(), &scr->priv->timeout, + &scr->priv->interval, + &scr->priv->prefer_blanking, + &scr->priv->allow_exposures); + XSetScreenSaver(GDK_DISPLAY(), 0, 0, + DontPreferBlanking, DontAllowExposures); + XUnlockDisplay (GDK_DISPLAY()); +} + +static void +screensaver_init_x11 (TotemScrsaver *scr) +{ +#ifdef HAVE_XTEST + int a, b, c, d; + + XLockDisplay (GDK_DISPLAY()); + scr->priv->have_xtest = (XTestQueryExtension (GDK_DISPLAY(), &a, &b, &c, &d) == True); + if (scr->priv->have_xtest != FALSE) + { + scr->priv->keycode1 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); + if (scr->priv->keycode1 == 0) { + g_warning ("scr->priv->keycode1 not existant"); + } + scr->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R); + if (scr->priv->keycode2 == 0) { + scr->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); + if (scr->priv->keycode2 == 0) { + g_warning ("scr->priv->keycode2 not existant"); + } + } + scr->priv->keycode = &scr->priv->keycode1; + } + XUnlockDisplay (GDK_DISPLAY()); +#endif /* HAVE_XTEST */ +} + +static void +screensaver_finalize_x11 (TotemScrsaver *scr) +{ + g_source_remove_by_user_data (scr); +} +#endif + +static void +totem_scrsaver_class_init (TotemScrsaverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->finalize = totem_scrsaver_finalize; +} + +TotemScrsaver * +totem_scrsaver_new (void) +{ + return TOTEM_SCRSAVER (g_object_new (TOTEM_TYPE_SCRSAVER, NULL)); +} + +static void +totem_scrsaver_init (TotemScrsaver *scr) +{ + scr->priv = g_new0 (TotemScrsaverPrivate, 1); + + screensaver_init_dbus (scr); +#ifdef GDK_WINDOWING_X11 + screensaver_init_x11 (scr); +#else +#warning Unimplemented +#endif +} + +void +totem_scrsaver_disable (TotemScrsaver *scr) +{ + if (scr->priv->disabled != FALSE) + return; + + scr->priv->disabled = TRUE; + + if (screensaver_is_running_dbus (scr) != FALSE) + screensaver_disable_dbus (scr); + else +#ifdef GDK_WINDOWING_X11 + screensaver_disable_x11 (scr); +#else +#warning Unimplemented + {} +#endif +} + +void +totem_scrsaver_enable (TotemScrsaver *scr) +{ + if (scr->priv->disabled == FALSE) + return; + + scr->priv->disabled = FALSE; + + if (screensaver_is_running_dbus (scr) != FALSE) + screensaver_enable_dbus (scr); + else +#ifdef GDK_WINDOWING_X11 + screensaver_enable_x11 (scr); +#else +#warning Unimplemented + {} +#endif +} + +void +totem_scrsaver_set_state (TotemScrsaver *scr, gboolean enable) +{ + if (scr->priv->disabled == !enable) + return; + + scr->priv->disabled = !enable; + if (scr->priv->disabled != FALSE) + totem_scrsaver_disable (scr); + else + totem_scrsaver_enable (scr); +} + +static void +totem_scrsaver_finalize (GObject *object) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (object); + + screensaver_finalize_dbus (scr); +#ifdef GDK_WINDOWING_X11 + screensaver_finalize_x11 (scr); +#else +#warning Unimplemented + {} +#endif + + g_free (scr->priv); + + if (G_OBJECT_CLASS (parent_class)->finalize != NULL) { + (* G_OBJECT_CLASS (parent_class)->finalize) (object); + } +} + diff --git a/trunk/src/totem-scrsaver.h b/trunk/src/totem-scrsaver.h new file mode 100644 index 000000000..93d469f44 --- /dev/null +++ b/trunk/src/totem-scrsaver.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2004, Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include <glib.h> +#include <glib-object.h> + +#define TOTEM_TYPE_SCRSAVER (totem_scrsaver_get_type ()) +#define TOTEM_SCRSAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_SCRSAVER, TotemScrsaver)) +#define TOTEM_SCRSAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_SCRSAVER, TotemScrsaverClass)) +#define TOTEM_IS_SCRSAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_SCRSAVER)) +#define TOTEM_IS_SCRSAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_SCRSAVER)) + + +typedef struct TotemScrsaver TotemScrsaver; +typedef struct TotemScrsaverClass TotemScrsaverClass; +typedef struct TotemScrsaverPrivate TotemScrsaverPrivate; + +struct TotemScrsaver { + GObject parent; + TotemScrsaverPrivate *priv; +}; + +struct TotemScrsaverClass { + GObjectClass parent_class; +}; + +GType totem_scrsaver_get_type (void); +TotemScrsaver *totem_scrsaver_new (void); +void totem_scrsaver_enable (TotemScrsaver *scr); +void totem_scrsaver_disable (TotemScrsaver *scr); +void totem_scrsaver_set_state (TotemScrsaver *scr, + gboolean enable); + diff --git a/trunk/src/totem-session.c b/trunk/src/totem-session.c new file mode 100644 index 000000000..151c01c89 --- /dev/null +++ b/trunk/src/totem-session.c @@ -0,0 +1,172 @@ +/* totem-session.c + + Copyright (C) 2004 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" + +#include "totem.h" +#include "totem-private.h" +#include "totem-session.h" + +#ifndef HAVE_GTK_ONLY + +#include <libgnome/gnome-config.h> +#include <libgnomeui/gnome-client.h> + +static char * +totem_session_create_key (void) +{ + char *filename, *path; + + filename = g_strdup_printf ("totem-%d-%d-%u.pls", + (int) getpid (), + (int) time (NULL), + g_random_int ()); + path = g_build_filename (g_get_home_dir (), + ".gnome2", filename, NULL); + g_free (filename); + + return path; +} + +static gboolean +totem_save_yourself_cb (GnomeClient *client, int phase, GnomeSaveStyle style, + gboolean shutting_down, GnomeInteractStyle interact_style, + gboolean fast, Totem *totem) +{ + char *argv[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + int i = 0; + char *path_id, *current, *seek, *uri; + + if (style == GNOME_SAVE_GLOBAL) + return TRUE; + + path_id = totem_session_create_key (); + totem_playlist_save_current_playlist (totem->playlist, path_id); + + /* How to discard the save */ + argv[i++] = "rm"; + argv[i++] = "-f"; + argv[i++] = path_id; + gnome_client_set_discard_command (client, i, argv); + + /* How to clone or restart */ + i = 0; + current = g_strdup_printf ("%d", + totem_playlist_get_current (totem->playlist)); + seek = g_strdup_printf ("%"G_GINT64_FORMAT, + bacon_video_widget_get_current_time (totem->bvw)); + argv[i++] = (char *) totem->argv0; + argv[i++] = "--playlist-idx"; + argv[i++] = current; + argv[i++] = "--seek"; + argv[i++] = seek; + + uri = g_filename_to_uri (path_id, NULL, NULL); + argv[i++] = uri; + + gnome_client_set_clone_command (client, i, argv); + gnome_client_set_restart_command (client, i, argv); + + g_free (path_id); + g_free (current); + g_free (seek); + g_free (uri); + + return TRUE; +} + +static void +totem_client_die_cb (GnomeClient *client, Totem *totem) +{ + totem_action_exit (totem); +} + +void +totem_session_setup (Totem *totem, char **argv) +{ + GnomeClient *client; + GnomeClientFlags flags; + + totem->argv0 = argv[0]; + + client = gnome_master_client (); + g_signal_connect (G_OBJECT (client), "save-yourself", + G_CALLBACK (totem_save_yourself_cb), totem); + g_signal_connect (G_OBJECT (client), "die", + G_CALLBACK (totem_client_die_cb), totem); + + flags = gnome_client_get_flags (client); + if (flags & GNOME_CLIENT_RESTORED) + totem->session_restored = TRUE; +} + +void +totem_session_restore (Totem *totem, char **filenames) +{ + char *mrl, *uri; + + g_return_if_fail (filenames[0] != NULL); + uri = filenames[0]; + + totem_signal_block_by_data (totem->playlist, totem); + + if (totem_playlist_add_mrl (totem->playlist, uri, NULL) == FALSE) + { + totem_signal_unblock_by_data (totem->playlist, totem); + totem_action_set_mrl (totem, NULL); + g_free (uri); + return; + } + + totem_signal_unblock_by_data (totem->playlist, totem); + + if (totem->index != 0) + totem_playlist_set_current (totem->playlist, totem->index); + mrl = totem_playlist_get_current_mrl (totem->playlist); + + totem_action_set_mrl_with_warning (totem, mrl, FALSE); + + if (totem->seek_to != 0) + { + bacon_video_widget_seek_time (totem->bvw, + totem->seek_to, NULL); + } + bacon_video_widget_pause (totem->bvw); + + g_free (mrl); + + return; +} + +#else + +void +totem_session_setup (Totem *totem, char **argv) +{ +} + +void +totem_session_restore (Totem *totem, char **argv) +{ +} + +#endif /* !HAVE_GTK_ONLY */ diff --git a/trunk/src/totem-session.h b/trunk/src/totem-session.h new file mode 100644 index 000000000..02a295d54 --- /dev/null +++ b/trunk/src/totem-session.h @@ -0,0 +1,35 @@ +/* totem-session.h + + Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_SESSION_H +#define TOTEM_SESSION_H + +#include "totem.h" + +G_BEGIN_DECLS + +void totem_session_setup (Totem *totem, char **argv); +void totem_session_restore (Totem *totem, char **filenames); + +G_END_DECLS + +#endif /* TOTEM_SESSION_H */ diff --git a/trunk/src/totem-sidebar.c b/trunk/src/totem-sidebar.c new file mode 100644 index 000000000..2960074c5 --- /dev/null +++ b/trunk/src/totem-sidebar.c @@ -0,0 +1,149 @@ +/* totem-sidebar.c + + Copyright (C) 2004-2005 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" + +#include <glib/gi18n.h> + +#include "totem.h" +#include "totem-sidebar.h" +#include "totem-private.h" +#include "ev-sidebar.h" + +static void +cb_resize (Totem * totem) +{ + GValue gvalue_size = { 0, }; + gint handle_size; + GtkWidget *pane; + int w, h; + + w = totem->win->allocation.width; + h = totem->win->allocation.height; + + g_value_init (&gvalue_size, G_TYPE_INT); + pane = glade_xml_get_widget (totem->xml, "tmw_main_pane"); + gtk_widget_style_get_property(pane, "handle-size", &gvalue_size); + handle_size = g_value_get_int (&gvalue_size); + + if (totem->sidebar_shown) { + w += totem->sidebar->allocation.width + handle_size; + } else { + w -= totem->sidebar->allocation.width + handle_size; + } + + if (w > 0 && h > 0) + gtk_window_resize (GTK_WINDOW (totem->win), w, h); +} + +void +totem_sidebar_toggle (Totem *totem, gboolean state) +{ + GtkAction *action; + + if (GTK_WIDGET_VISIBLE (GTK_WIDGET (totem->sidebar)) == state) + return; + + if (state != FALSE) + gtk_widget_show (GTK_WIDGET (totem->sidebar)); + else + gtk_widget_hide (GTK_WIDGET (totem->sidebar)); + + action = gtk_action_group_get_action (totem->main_action_group, "sidebar"); + totem_signal_block_by_data (G_OBJECT (action), totem); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), state); + totem_signal_unblock_by_data (G_OBJECT (action), totem); + + gconf_client_set_bool (totem->gc, + GCONF_PREFIX"/sidebar_shown", + state, + NULL); + totem->sidebar_shown = state; + cb_resize(totem); +} + +static void +toggle_sidebar_from_sidebar (GtkWidget *playlist, Totem *totem) +{ + totem_sidebar_toggle (totem, FALSE); +} + +gboolean +totem_sidebar_is_visible (Totem *totem) +{ + return totem->sidebar_shown; +} + +void +totem_sidebar_setup (Totem *totem, gboolean visible, const char *page_id) +{ + GtkWidget *item; + GtkAction *action; + + item = glade_xml_get_widget (totem->xml, "tmw_main_pane"); + totem->sidebar = ev_sidebar_new (); + ev_sidebar_add_page (EV_SIDEBAR (totem->sidebar), + "playlist", _("Playlist"), + GTK_WIDGET (totem->playlist)); + ev_sidebar_add_page (EV_SIDEBAR (totem->sidebar), + "properties", _("Properties"), + GTK_WIDGET (totem->properties)); + if (page_id != NULL) { + ev_sidebar_set_current_page (EV_SIDEBAR (totem->sidebar), + page_id); + } else { + ev_sidebar_set_current_page (EV_SIDEBAR (totem->sidebar), + "playlist"); + } + gtk_widget_set_sensitive (GTK_WIDGET (totem->properties), FALSE); + gtk_paned_pack2 (GTK_PANED (item), totem->sidebar, FALSE, FALSE); + + totem->sidebar_shown = visible; + + action = gtk_action_group_get_action (totem->main_action_group, + "sidebar"); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible); + + /* Signals */ + g_signal_connect (G_OBJECT (totem->sidebar), "closed", + G_CALLBACK (toggle_sidebar_from_sidebar), totem); + + gtk_widget_show_all (totem->sidebar); + + if (!visible) + gtk_widget_hide (totem->sidebar); +} + +const char * +totem_sidebar_get_current_page (Totem *totem) +{ + return ev_sidebar_get_current_page (EV_SIDEBAR (totem->sidebar)); +} + +void +totem_sidebar_set_current_page (Totem *totem, const char *name) +{ + ev_sidebar_set_current_page (EV_SIDEBAR (totem->sidebar), name); + totem_sidebar_toggle (totem, TRUE); +} + diff --git a/trunk/src/totem-sidebar.h b/trunk/src/totem-sidebar.h new file mode 100644 index 000000000..def4c4720 --- /dev/null +++ b/trunk/src/totem-sidebar.h @@ -0,0 +1,38 @@ +/* totem-sidebar.h + + Copyright (C) 2004-2005 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_SIDEBAR_H +#define TOTEM_SIDEBAR_H + +G_BEGIN_DECLS + +void totem_sidebar_setup (Totem *totem, gboolean visible, + const char *page_id); +void totem_sidebar_toggle (Totem *totem, gboolean state); +void totem_sidebar_set_visibility (Totem *totem, gboolean visible); +gboolean totem_sidebar_is_visible (Totem *totem); +const char *totem_sidebar_get_current_page (Totem *totem); +void totem_sidebar_set_current_page (Totem *totem, const char *name); + +G_END_DECLS + +#endif /* TOTEM_SIDEBAR_H */ diff --git a/trunk/src/totem-skipto.c b/trunk/src/totem-skipto.c new file mode 100644 index 000000000..b3baa481a --- /dev/null +++ b/trunk/src/totem-skipto.c @@ -0,0 +1,196 @@ +/* totem-skipto.c + + Copyright (C) 2004 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" +#include "totem-skipto.h" +#include "video-utils.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glade/glade.h> +#include <gconf/gconf-client.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "debug.h" + +struct TotemSkiptoPrivate +{ + GladeXML *xml; + GtkWidget *label; + GtkWidget *spinbutton; + gint64 time; +}; + +static void totem_skipto_class_init (TotemSkiptoClass *class); +static void totem_skipto_init (TotemSkipto *skipto); + +G_DEFINE_TYPE(TotemSkipto, totem_skipto, GTK_TYPE_DIALOG) + +static void +totem_skipto_response_cb (GtkDialog *dialog, gint response_id, gpointer data) +{ + TotemSkipto *skipto; + + skipto = TOTEM_SKIPTO (dialog); + gtk_spin_button_update (GTK_SPIN_BUTTON (skipto->_priv->spinbutton)); +} + +static void +totem_skipto_init (TotemSkipto *skipto) +{ + skipto->_priv = g_new0 (TotemSkiptoPrivate, 1); + gtk_container_set_border_width (GTK_CONTAINER (skipto), 5); + + g_signal_connect (skipto, "response", + G_CALLBACK (totem_skipto_response_cb), NULL); +} + +static void +totem_skipto_finalize (GObject *object) +{ + g_return_if_fail (object != NULL); + + if (G_OBJECT_CLASS (totem_skipto_parent_class)->finalize != NULL) { + (* G_OBJECT_CLASS (totem_skipto_parent_class)->finalize) (object); + } +} + +void +totem_skipto_update_range (TotemSkipto *skipto, gint64 time) +{ + g_return_if_fail (TOTEM_IS_SKIPTO (skipto)); + + if (time == skipto->_priv->time) + return; + + gtk_spin_button_set_range (GTK_SPIN_BUTTON (skipto->_priv->spinbutton), + 0, (gdouble) time / 1000); + skipto->_priv->time = time; +} + +gint64 +totem_skipto_get_range (TotemSkipto *skipto) +{ + gint64 time; + + g_return_val_if_fail (TOTEM_IS_SKIPTO (skipto), 0); + + time = gtk_spin_button_get_value (GTK_SPIN_BUTTON (skipto->_priv->spinbutton)) * 1000; + + return time; +} + +void +totem_skipto_set_seekable (TotemSkipto *skipto, gboolean seekable) +{ + g_return_if_fail (TOTEM_IS_SKIPTO (skipto)); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (skipto), + GTK_RESPONSE_OK, seekable); +} + +void +totem_skipto_set_current (TotemSkipto *skipto, gint64 time) +{ + g_return_if_fail (TOTEM_IS_SKIPTO (skipto)); + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (skipto->_priv->spinbutton), + (gdouble) (time / 1000)); +} + +static void +spin_button_activate_cb (GtkEntry *entry, TotemSkipto *skipto) +{ + gtk_dialog_response (GTK_DIALOG (skipto), GTK_RESPONSE_OK); +} + +static void +spin_button_value_changed_cb (GtkSpinButton *spinbutton, TotemSkipto *skipto) +{ + int sec; + char *str; + + sec = (int) gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinbutton)); + str = totem_time_to_string_text (sec * 1000); + gtk_label_set_text (GTK_LABEL (skipto->_priv->label), str); + g_free (str); +} + +GtkWidget* +totem_skipto_new (const char *glade_filename) +{ + TotemSkipto *skipto; + GtkWidget *container, *item; + + g_return_val_if_fail (glade_filename != NULL, NULL); + + skipto = TOTEM_SKIPTO (g_object_new (GTK_TYPE_SKIPTO, NULL)); + + skipto->_priv->xml = glade_xml_new (glade_filename, "tstw_skip_vbox", NULL); + if (skipto->_priv->xml == NULL) + { + totem_skipto_finalize (G_OBJECT (skipto)); + return NULL; + } + skipto->_priv->label = glade_xml_get_widget + (skipto->_priv->xml, "tstw_position_label"); + skipto->_priv->spinbutton = glade_xml_get_widget + (skipto->_priv->xml, "tstw_skip_spinbutton"); + + gtk_window_set_title (GTK_WINDOW (skipto), _("Skip to")); + gtk_dialog_set_has_separator (GTK_DIALOG (skipto), FALSE); + gtk_dialog_add_buttons (GTK_DIALOG (skipto), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + + /* Skipto dialog */ + item = glade_xml_get_widget (skipto->_priv->xml, + "totem_skipto_window"); + g_signal_connect (G_OBJECT (skipto->_priv->spinbutton), "value-changed", + G_CALLBACK (spin_button_value_changed_cb), skipto); + g_signal_connect_after (G_OBJECT (skipto->_priv->spinbutton), "activate", + G_CALLBACK (spin_button_activate_cb), skipto); + + container = glade_xml_get_widget (skipto->_priv->xml, + "tstw_skip_vbox"); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (skipto)->vbox), + container, + TRUE, /* expand */ + TRUE, /* fill */ + 0); /* padding */ + + gtk_widget_show_all (GTK_DIALOG (skipto)->vbox); + + return GTK_WIDGET (skipto); +} + +static void +totem_skipto_class_init (TotemSkiptoClass *klass) +{ + G_OBJECT_CLASS (klass)->finalize = totem_skipto_finalize; +} + diff --git a/trunk/src/totem-skipto.h b/trunk/src/totem-skipto.h new file mode 100644 index 000000000..bff3bbc24 --- /dev/null +++ b/trunk/src/totem-skipto.h @@ -0,0 +1,58 @@ +/* totem-skipto.h + + Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_SKIPTO_H +#define TOTEM_SKIPTO_H + +#include <gtk/gtkdialog.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_SKIPTO (totem_skipto_get_type ()) +#define TOTEM_SKIPTO(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_SKIPTO, TotemSkipto)) +#define TOTEM_SKIPTO_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_SKIPTO, TotemSkiptoClass)) +#define TOTEM_IS_SKIPTO(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_SKIPTO)) +#define TOTEM_IS_SKIPTO_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SKIPTO)) + +typedef struct TotemSkipto TotemSkipto; +typedef struct TotemSkiptoClass TotemSkiptoClass; +typedef struct TotemSkiptoPrivate TotemSkiptoPrivate; + +struct TotemSkipto { + GtkDialog parent; + TotemSkiptoPrivate *_priv; +}; + +struct TotemSkiptoClass { + GtkDialogClass parent_class; +}; + +GtkType totem_skipto_get_type (void); +GtkWidget *totem_skipto_new (const char *glade_filename); +gint64 totem_skipto_get_range (TotemSkipto *skipto); +void totem_skipto_update_range (TotemSkipto *skipto, gint64 time); +void totem_skipto_set_seekable (TotemSkipto *skipto, gboolean seekable); +void totem_skipto_set_current (TotemSkipto *skipto, gint64 time); + +G_END_DECLS + +#endif /* TOTEM_SKIPTO_H */ diff --git a/trunk/src/totem-statusbar.c b/trunk/src/totem-statusbar.c new file mode 100644 index 000000000..5fbebcc34 --- /dev/null +++ b/trunk/src/totem-statusbar.c @@ -0,0 +1,674 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * TotemStatusbar Copyright (C) 1998 Shawn T. Amundson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include "config.h" + +#include <gtk/gtkframe.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkvseparator.h> +#include <gtk/gtkwindow.h> +#include <glib/gi18n.h> + +#include "totem-statusbar.h" +#include "video-utils.h" + +static void totem_statusbar_class_init (TotemStatusbarClass *class); +static void totem_statusbar_init (TotemStatusbar *statusbar); +static void totem_statusbar_destroy (GtkObject *object); +static void totem_statusbar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void totem_statusbar_realize (GtkWidget *widget); +static void totem_statusbar_unrealize (GtkWidget *widget); +static void totem_statusbar_map (GtkWidget *widget); +static void totem_statusbar_unmap (GtkWidget *widget); +static gboolean totem_statusbar_button_press (GtkWidget *widget, + GdkEventButton *event); +static gboolean totem_statusbar_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static void totem_statusbar_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void totem_statusbar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void totem_statusbar_create_window (TotemStatusbar *statusbar); +static void totem_statusbar_destroy_window (TotemStatusbar *statusbar); +static void totem_statusbar_sync_description (TotemStatusbar *statusbar); + +static GtkContainerClass *parent_class; + +G_DEFINE_TYPE(TotemStatusbar, totem_statusbar, GTK_TYPE_HBOX) + +static void +totem_statusbar_class_init (TotemStatusbarClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkContainerClass *container_class; + + object_class = (GtkObjectClass *) class; + widget_class = (GtkWidgetClass *) class; + container_class = (GtkContainerClass *) class; + + parent_class = g_type_class_peek_parent (class); + + object_class->destroy = totem_statusbar_destroy; + + widget_class->realize = totem_statusbar_realize; + widget_class->unrealize = totem_statusbar_unrealize; + widget_class->map = totem_statusbar_map; + widget_class->unmap = totem_statusbar_unmap; + + widget_class->button_press_event = totem_statusbar_button_press; + widget_class->expose_event = totem_statusbar_expose_event; + + widget_class->size_request = totem_statusbar_size_request; + widget_class->size_allocate = totem_statusbar_size_allocate; + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_enum ("shadow_type", + _("Shadow type"), + _("Style of bevel around the statusbar text"), + GTK_TYPE_SHADOW_TYPE, + GTK_SHADOW_IN, + G_PARAM_READABLE)); +} + +static void +totem_statusbar_init (TotemStatusbar *statusbar) +{ + GtkBox *box; + GtkShadowType shadow_type; + GtkWidget *packer, *hbox; + AtkObject *obj; + + box = GTK_BOX (statusbar); + + box->spacing = 2; + box->homogeneous = FALSE; + + statusbar->has_resize_grip = TRUE; + statusbar->time = 0; + statusbar->length = -1; + + gtk_widget_style_get (GTK_WIDGET (statusbar), "shadow_type", &shadow_type, NULL); + + statusbar->frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (statusbar->frame), shadow_type); + gtk_box_pack_start (box, statusbar->frame, TRUE, TRUE, 0); + gtk_widget_show (statusbar->frame); + hbox = gtk_hbox_new (FALSE, 4); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); + gtk_widget_show (hbox); + + statusbar->label = gtk_label_new (_("Stopped")); + gtk_misc_set_alignment (GTK_MISC (statusbar->label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->label, FALSE, FALSE, 0); + gtk_widget_show (statusbar->label); + + /* progressbar for network streams */ + statusbar->progress = gtk_progress_bar_new (); + gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (statusbar->progress), + GTK_ORIENTATION_HORIZONTAL); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (statusbar->progress), 0.); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->progress, FALSE, TRUE, 0); + gtk_widget_set_size_request (statusbar->progress, 150, 10); + gtk_widget_hide (statusbar->progress); + + packer = gtk_vseparator_new (); + gtk_box_pack_start (GTK_BOX (hbox), packer, FALSE, FALSE, 0); + gtk_widget_show (packer); + + statusbar->time_label = gtk_label_new (_("0:00 / 0:00")); + gtk_misc_set_alignment (GTK_MISC (statusbar->label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox), statusbar->time_label, FALSE, FALSE, 0); + gtk_widget_show (statusbar->time_label); + + + obj = gtk_widget_get_accessible (GTK_WIDGET (box)); + atk_object_set_role (obj, ATK_ROLE_STATUSBAR); + totem_statusbar_sync_description (statusbar); + + /* don't expand the size request for the label; if we + * do that then toplevels weirdly resize + */ + gtk_container_add (GTK_CONTAINER (statusbar->frame), hbox); +} + +GtkWidget* +totem_statusbar_new (void) +{ + return g_object_new (TOTEM_TYPE_STATUSBAR, NULL); +} + +GtkWidget * +totem_statusbar_new_from_glade (gchar *widget_name, + gchar *string1, gchar *string2, + gint int1, gint int2) +{ + GtkWidget *widget; + + widget = totem_statusbar_new (); + gtk_widget_show (widget); + + return widget; +} + +static void +totem_statusbar_update_time (TotemStatusbar *statusbar) +{ + char *time, *length, *label; + + time = totem_time_to_string (statusbar->time * 1000); + + if (statusbar->length < 0) { + label = g_strdup_printf (_("%s (Streaming)"), time); + } else { + length = totem_time_to_string + (statusbar->length == -1 ? 0 : statusbar->length * 1000); + + if (statusbar->seeking == FALSE) + /* Elapsed / Total Length */ + label = g_strdup_printf (_("%s / %s"), time, length); + else + /* Seeking to Time / Total Length */ + label = g_strdup_printf (_("Seek to %s / %s"), time, length); + + g_free (length); + } + g_free (time); + + gtk_label_set_text (GTK_LABEL (statusbar->time_label), label); + g_free (label); + + totem_statusbar_sync_description (statusbar); +} + +void +totem_statusbar_set_text (TotemStatusbar *statusbar, const char *label) +{ + gtk_label_set_text (GTK_LABEL (statusbar->label), label); + g_free (statusbar->saved_label); + statusbar->saved_label = g_strdup (label); + + totem_statusbar_sync_description (statusbar); +} + +void +totem_statusbar_set_time (TotemStatusbar *statusbar, gint time) +{ + g_return_if_fail (TOTEM_IS_STATUSBAR (statusbar)); + + if (statusbar->time == time) + return; + + statusbar->time = time; + totem_statusbar_update_time (statusbar); +} + +static gboolean +totem_statusbar_timeout_pop (TotemStatusbar *statusbar) +{ + gtk_label_set_text (GTK_LABEL (statusbar->label), statusbar->saved_label); + g_free (statusbar->saved_label); + statusbar->saved_label = NULL; + statusbar->pushed = FALSE; + gtk_widget_hide (statusbar->progress); + + totem_statusbar_sync_description (statusbar); + + statusbar->percentage = 101; + + return FALSE; +} + +void +totem_statusbar_push (TotemStatusbar *statusbar, guint percentage) +{ + char *label; + statusbar->pushed = TRUE; + + if (statusbar->timeout != 0) + { + g_source_remove (statusbar->timeout); + if (statusbar->percentage == percentage) { + statusbar->timeout = g_timeout_add (3000, + (GSourceFunc) totem_statusbar_timeout_pop, statusbar); + return; + } + } + + statusbar->percentage = percentage; + + if (statusbar->saved_label == NULL) + { + statusbar->saved_label = g_strdup + (gtk_label_get_text (GTK_LABEL (statusbar->label))); + } + + gtk_label_set_text (GTK_LABEL (statusbar->label), _("Buffering")); + + /* eg: 75 % */ + label = g_strdup_printf (_("%d %%"), percentage); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (statusbar->progress), label); + g_free (label); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (statusbar->progress), + percentage / 100.); + gtk_widget_show (statusbar->progress); + + statusbar->timeout = g_timeout_add (3000, + (GSourceFunc) totem_statusbar_timeout_pop, statusbar); + + totem_statusbar_sync_description (statusbar); +} + +void +totem_statusbar_pop (TotemStatusbar *statusbar) +{ + if (statusbar->pushed != FALSE) + { + g_source_remove (statusbar->timeout); + totem_statusbar_timeout_pop (statusbar); + } +} + +void +totem_statusbar_set_time_and_length (TotemStatusbar *statusbar, + gint time, gint length) +{ + g_return_if_fail (TOTEM_IS_STATUSBAR (statusbar)); + + if (time != statusbar->time || + length != statusbar->length) { + statusbar->time = time; + statusbar->length = length; + + totem_statusbar_update_time (statusbar); + } +} + +void +totem_statusbar_set_seeking (TotemStatusbar *statusbar, + gboolean seeking) +{ + g_return_if_fail (TOTEM_IS_STATUSBAR (statusbar)); + + if (statusbar->seeking == seeking) + return; + + statusbar->seeking = seeking; + + totem_statusbar_update_time (statusbar); +} + +static void +totem_statusbar_sync_description (TotemStatusbar *statusbar) +{ + AtkObject *obj; + char *text; + + obj = gtk_widget_get_accessible (GTK_WIDGET (statusbar)); + if (statusbar->pushed == FALSE) { + /* eg: Paused, 0:32 / 1:05 */ + text = g_strdup_printf (_("%s, %s"), + gtk_label_get_text (GTK_LABEL (statusbar->label)), + gtk_label_get_text (GTK_LABEL (statusbar->time_label))); + } else { + /* eg: Buffering, 75 % */ + text = g_strdup_printf (_("%s, %d %%"), + gtk_label_get_text (GTK_LABEL (statusbar->label)), + statusbar->percentage); + } + + atk_object_set_name (obj, text); + g_free (text); +} + +void +totem_statusbar_set_has_resize_grip (TotemStatusbar *statusbar, + gboolean setting) +{ + g_return_if_fail (TOTEM_IS_STATUSBAR (statusbar)); + + setting = setting != FALSE; + + if (setting != statusbar->has_resize_grip) + { + statusbar->has_resize_grip = setting; + gtk_widget_queue_draw (GTK_WIDGET (statusbar)); + + if (GTK_WIDGET_REALIZED (statusbar)) + { + if (statusbar->has_resize_grip && statusbar->grip_window == NULL) + totem_statusbar_create_window (statusbar); + else if (!statusbar->has_resize_grip && statusbar->grip_window != NULL) + totem_statusbar_destroy_window (statusbar); + } + } +} + +gboolean +totem_statusbar_get_has_resize_grip (TotemStatusbar *statusbar) +{ + g_return_val_if_fail (TOTEM_IS_STATUSBAR (statusbar), FALSE); + + return statusbar->has_resize_grip; +} + +static void +totem_statusbar_destroy (GtkObject *object) +{ + TotemStatusbar *statusbar; + TotemStatusbarClass *class; + + g_return_if_fail (TOTEM_IS_STATUSBAR (object)); + + statusbar = TOTEM_STATUSBAR (object); + class = TOTEM_STATUSBAR_GET_CLASS (statusbar); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static GdkWindowEdge +get_grip_edge (TotemStatusbar *statusbar) +{ + GtkWidget *widget = GTK_WIDGET (statusbar); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + return GDK_WINDOW_EDGE_SOUTH_EAST; + else + return GDK_WINDOW_EDGE_SOUTH_WEST; +} + +static void +get_grip_rect (TotemStatusbar *statusbar, + GdkRectangle *rect) +{ + GtkWidget *widget; + gint w, h; + + widget = GTK_WIDGET (statusbar); + + /* These are in effect the max/default size of the grip. */ + w = 18; + h = 18; + + if (w > (widget->allocation.width)) + w = widget->allocation.width; + + if (h > (widget->allocation.height - widget->style->ythickness)) + h = widget->allocation.height - widget->style->ythickness; + + rect->width = w; + rect->height = h; + rect->y = widget->allocation.y + widget->allocation.height - h; + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + rect->x = widget->allocation.x + widget->allocation.width - w; + else + rect->x = widget->allocation.x + widget->style->xthickness; +} + +static void +totem_statusbar_create_window (TotemStatusbar *statusbar) +{ + GtkWidget *widget; + GdkWindowAttr attributes; + gint attributes_mask; + GdkRectangle rect; + + g_return_if_fail (GTK_WIDGET_REALIZED (statusbar)); + g_return_if_fail (statusbar->has_resize_grip); + + widget = GTK_WIDGET (statusbar); + + get_grip_rect (statusbar, &rect); + + attributes.x = rect.x; + attributes.y = rect.y; + attributes.width = rect.width; + attributes.height = rect.height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_BUTTON_PRESS_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + statusbar->grip_window = gdk_window_new (widget->window, + &attributes, attributes_mask); + gdk_window_set_user_data (statusbar->grip_window, widget); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + statusbar->cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER); + else + statusbar->cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER); + gdk_window_set_cursor (statusbar->grip_window, statusbar->cursor); +} + +static void +totem_statusbar_destroy_window (TotemStatusbar *statusbar) +{ + gdk_window_set_user_data (statusbar->grip_window, NULL); + gdk_window_destroy (statusbar->grip_window); + gdk_cursor_unref (statusbar->cursor); + statusbar->cursor = NULL; + statusbar->grip_window = NULL; +} + +static void +totem_statusbar_realize (GtkWidget *widget) +{ + TotemStatusbar *statusbar; + + statusbar = TOTEM_STATUSBAR (widget); + + (* GTK_WIDGET_CLASS (parent_class)->realize) (widget); + + if (statusbar->has_resize_grip) + totem_statusbar_create_window (statusbar); +} + +static void +totem_statusbar_unrealize (GtkWidget *widget) +{ + TotemStatusbar *statusbar; + + statusbar = TOTEM_STATUSBAR (widget); + + if (statusbar->grip_window) + totem_statusbar_destroy_window (statusbar); + + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +totem_statusbar_map (GtkWidget *widget) +{ + TotemStatusbar *statusbar; + + statusbar = TOTEM_STATUSBAR (widget); + + (* GTK_WIDGET_CLASS (parent_class)->map) (widget); + + if (statusbar->grip_window) + gdk_window_show (statusbar->grip_window); +} + +static void +totem_statusbar_unmap (GtkWidget *widget) +{ + TotemStatusbar *statusbar; + + statusbar = TOTEM_STATUSBAR (widget); + + if (statusbar->grip_window) + gdk_window_hide (statusbar->grip_window); + + (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget); +} + +static gboolean +totem_statusbar_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + TotemStatusbar *statusbar; + GtkWidget *ancestor; + GdkWindowEdge edge; + + statusbar = TOTEM_STATUSBAR (widget); + + if (!statusbar->has_resize_grip || + event->type != GDK_BUTTON_PRESS) + return FALSE; + + ancestor = gtk_widget_get_toplevel (widget); + + if (!GTK_IS_WINDOW (ancestor)) + return FALSE; + + edge = get_grip_edge (statusbar); + + if (event->button == 1) + gtk_window_begin_resize_drag (GTK_WINDOW (ancestor), + edge, + event->button, + event->x_root, event->y_root, + event->time); + else if (event->button == 2) + gtk_window_begin_move_drag (GTK_WINDOW (ancestor), + event->button, + event->x_root, event->y_root, + event->time); + else + return FALSE; + + return TRUE; +} + +static gboolean +totem_statusbar_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + TotemStatusbar *statusbar; + GdkRectangle rect; + + statusbar = TOTEM_STATUSBAR (widget); + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (statusbar->has_resize_grip) + { + GdkWindowEdge edge; + + edge = get_grip_edge (statusbar); + + get_grip_rect (statusbar, &rect); + + gtk_paint_resize_grip (widget->style, + widget->window, + GTK_WIDGET_STATE (widget), + NULL, + widget, + "statusbar", + edge, + rect.x, rect.y, + /* don't draw grip over the frame, though you + * can click on the frame. + */ + rect.width - widget->style->xthickness, + rect.height - widget->style->ythickness); + } + + return FALSE; +} + +static void +totem_statusbar_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + TotemStatusbar *statusbar; + GtkShadowType shadow_type; + + statusbar = TOTEM_STATUSBAR (widget); + + gtk_widget_style_get (GTK_WIDGET (statusbar), "shadow_type", &shadow_type, NULL); + gtk_frame_set_shadow_type (GTK_FRAME (statusbar->frame), shadow_type); + + GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition); + + if (statusbar->has_resize_grip) + { + GdkRectangle rect; + + /* x, y in the grip rect depend on size allocation, but + * w, h do not so this is OK + */ + get_grip_rect (statusbar, &rect); + + requisition->width += rect.width; + requisition->height = MAX (requisition->height, rect.height); + } +} + +static void +totem_statusbar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + TotemStatusbar *statusbar; + + statusbar = TOTEM_STATUSBAR (widget); + + if (statusbar->has_resize_grip) + { + GdkRectangle rect; + GtkRequisition saved_req; + + widget->allocation = *allocation; /* get_grip_rect needs this info */ + get_grip_rect (statusbar, &rect); + + if (statusbar->grip_window) + gdk_window_move_resize (statusbar->grip_window, + rect.x, rect.y, + rect.width, rect.height); + + /* enter the bad hack zone */ + saved_req = widget->requisition; + widget->requisition.width -= rect.width; /* HBox::size_allocate needs this */ + if (widget->requisition.width < 0) + widget->requisition.width = 0; + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + widget->requisition = saved_req; + } + else + { + /* chain up normally */ + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + } +} + +/* + * vim: sw=2 ts=8 cindent noai bs=2 + */ diff --git a/trunk/src/totem-statusbar.h b/trunk/src/totem-statusbar.h new file mode 100644 index 000000000..8fa961f22 --- /dev/null +++ b/trunk/src/totem-statusbar.h @@ -0,0 +1,105 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * TotemStatusbar Copyright (C) 1998 Shawn T. Amundson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __TOTEM_STATUSBAR_H__ +#define __TOTEM_STATUSBAR_H__ + +#include <gtk/gtkhbox.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define TOTEM_TYPE_STATUSBAR (totem_statusbar_get_type ()) +#define TOTEM_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_STATUSBAR, TotemStatusbar)) +#define TOTEM_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_STATUSBAR, TotemStatusbarClass)) +#define TOTEM_IS_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_STATUSBAR)) +#define TOTEM_IS_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_STATUSBAR)) +#define TOTEM_STATUSBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TOTEM_TYPE_STATUSBAR, TotemStatusbarClass)) + + +typedef struct _TotemStatusbar TotemStatusbar; +typedef struct _TotemStatusbarClass TotemStatusbarClass; + +struct _TotemStatusbar +{ + GtkHBox parent_widget; + + GtkWidget *frame; + GtkWidget *label; + GtkWidget *progress; + GtkWidget *time_label; + + gint time; + gint length; + char *saved_label; + guint timeout; + guint percentage; + + GdkWindow *grip_window; + GdkCursor *cursor; + + guint has_resize_grip : 1; + guint pushed : 1; + guint seeking : 1; +}; + +struct _TotemStatusbarClass +{ + GtkHBoxClass parent_class; +}; + + +GType totem_statusbar_get_type (void) G_GNUC_CONST; +GtkWidget* totem_statusbar_new (void); + +void totem_statusbar_set_time (TotemStatusbar *statusbar, + gint time); +void totem_statusbar_set_time_and_length (TotemStatusbar *statusbar, + gint time, gint length); +void totem_statusbar_set_seeking (TotemStatusbar *statusbar, + gboolean seeking); + +void totem_statusbar_set_text (TotemStatusbar *statusbar, + const char *label); +void totem_statusbar_push (TotemStatusbar *statusbar, + guint percentage); +void totem_statusbar_pop (TotemStatusbar *statusbar); + +void totem_statusbar_set_has_resize_grip (TotemStatusbar *statusbar, + gboolean setting); +gboolean totem_statusbar_get_has_resize_grip (TotemStatusbar *statusbar); + +GtkWidget *totem_statusbar_new_from_glade (gchar *widget_name, + gchar *string1, gchar *string2, + gint int1, gint int2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __TOTEM_STATUSBAR_H__ */ diff --git a/trunk/src/totem-subtitle-encoding.c b/trunk/src/totem-subtitle-encoding.c new file mode 100644 index 000000000..693914b6b --- /dev/null +++ b/trunk/src/totem-subtitle-encoding.c @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2001-2006 Bastien Nocera <hadess@hadess.net> + * + * encoding list copied from gnome-terminal/encoding.c + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include "config.h" +#include <glib/gi18n.h> +#include "totem-subtitle-encoding.h" +#include <string.h> + +typedef enum +{ + SUBTITLE_ENCODING_CURRENT_LOCALE, + + SUBTITLE_ENCODING_ISO_8859_6, + SUBTITLE_ENCODING_IBM_864, + SUBTITLE_ENCODING_MAC_ARABIC, + SUBTITLE_ENCODING_WINDOWS_1256, + + SUBTITLE_ENCODING_ARMSCII_8, + + SUBTITLE_ENCODING_ISO_8859_4, + SUBTITLE_ENCODING_ISO_8859_13, + SUBTITLE_ENCODING_WINDOWS_1257, + + SUBTITLE_ENCODING_ISO_8859_14, + + SUBTITLE_ENCODING_ISO_8859_2, + SUBTITLE_ENCODING_IBM_852, + SUBTITLE_ENCODING_MAC_CE, + SUBTITLE_ENCODING_WINDOWS_1250, + + SUBTITLE_ENCODING_GB18030, + SUBTITLE_ENCODING_GB2312, + SUBTITLE_ENCODING_GBK, + SUBTITLE_ENCODING_HZ, + + SUBTITLE_ENCODING_BIG5, + SUBTITLE_ENCODING_BIG5_HKSCS, + SUBTITLE_ENCODING_EUC_TW, + + SUBTITLE_ENCODING_MAC_CROATIAN, + + SUBTITLE_ENCODING_ISO_8859_5, + SUBTITLE_ENCODING_IBM_855, + SUBTITLE_ENCODING_ISO_IR_111, + SUBTITLE_ENCODING_KOI8_R, + SUBTITLE_ENCODING_MAC_CYRILLIC, + SUBTITLE_ENCODING_WINDOWS_1251, + + SUBTITLE_ENCODING_CP_866, + + SUBTITLE_ENCODING_MAC_UKRAINIAN, + SUBTITLE_ENCODING_KOI8_U, + + SUBTITLE_ENCODING_GEOSTD8, + + SUBTITLE_ENCODING_ISO_8859_7, + SUBTITLE_ENCODING_MAC_GREEK, + SUBTITLE_ENCODING_WINDOWS_1253, + + SUBTITLE_ENCODING_MAC_GUJARATI, + + SUBTITLE_ENCODING_MAC_GURMUKHI, + + SUBTITLE_ENCODING_ISO_8859_8_I, + SUBTITLE_ENCODING_IBM_862, + SUBTITLE_ENCODING_MAC_HEBREW, + SUBTITLE_ENCODING_WINDOWS_1255, + + SUBTITLE_ENCODING_ISO_8859_8, + + SUBTITLE_ENCODING_MAC_DEVANAGARI, + + SUBTITLE_ENCODING_MAC_ICELANDIC, + + SUBTITLE_ENCODING_EUC_JP, + SUBTITLE_ENCODING_ISO_2022_JP, + SUBTITLE_ENCODING_SHIFT_JIS, + + SUBTITLE_ENCODING_EUC_KR, + SUBTITLE_ENCODING_ISO_2022_KR, + SUBTITLE_ENCODING_JOHAB, + SUBTITLE_ENCODING_UHC, + + SUBTITLE_ENCODING_ISO_8859_10, + + SUBTITLE_ENCODING_MAC_FARSI, + + SUBTITLE_ENCODING_ISO_8859_16, + SUBTITLE_ENCODING_MAC_ROMANIAN, + + SUBTITLE_ENCODING_ISO_8859_3, + + SUBTITLE_ENCODING_TIS_620, + + SUBTITLE_ENCODING_ISO_8859_9, + SUBTITLE_ENCODING_IBM_857, + SUBTITLE_ENCODING_MAC_TURKISH, + SUBTITLE_ENCODING_WINDOWS_1254, + + SUBTITLE_ENCODING_UTF_7, + SUBTITLE_ENCODING_UTF_8, + SUBTITLE_ENCODING_UTF_16, + SUBTITLE_ENCODING_UCS_2, + SUBTITLE_ENCODING_UCS_4, + + SUBTITLE_ENCODING_ISO_8859_1, + SUBTITLE_ENCODING_ISO_8859_15, + SUBTITLE_ENCODING_IBM_850, + SUBTITLE_ENCODING_MAC_ROMAN, + SUBTITLE_ENCODING_WINDOWS_1252, + + SUBTITLE_ENCODING_TCVN, + SUBTITLE_ENCODING_VISCII, + SUBTITLE_ENCODING_WINDOWS_1258, + + SUBTITLE_ENCODING_LAST +} SubtitleEncodingIndex; + + +typedef struct +{ + int index; + gboolean valid; + char *charset; + char *name; +} SubtitleEncoding; + + +static SubtitleEncoding encodings[] = { + + {SUBTITLE_ENCODING_CURRENT_LOCALE, TRUE, + NULL, N_("Current Locale")}, + + {SUBTITLE_ENCODING_ISO_8859_6, FALSE, + "ISO-8859-6", N_("Arabic")}, + {SUBTITLE_ENCODING_IBM_864, FALSE, + "IBM864", N_("Arabic")}, + {SUBTITLE_ENCODING_MAC_ARABIC, FALSE, + "MAC_ARABIC", N_("Arabic")}, + {SUBTITLE_ENCODING_WINDOWS_1256, FALSE, + "WINDOWS-1256", N_("Arabic")}, + + {SUBTITLE_ENCODING_ARMSCII_8, FALSE, + "ARMSCII-8", N_("Armenian")}, + + {SUBTITLE_ENCODING_ISO_8859_4, FALSE, + "ISO-8859-4", N_("Baltic")}, + {SUBTITLE_ENCODING_ISO_8859_13, FALSE, + "ISO-8859-13", N_("Baltic")}, + {SUBTITLE_ENCODING_WINDOWS_1257, FALSE, + "WINDOWS-1257", N_("Baltic")}, + + {SUBTITLE_ENCODING_ISO_8859_14, FALSE, + "ISO-8859-14", N_("Celtic")}, + + {SUBTITLE_ENCODING_ISO_8859_2, FALSE, + "ISO-8859-2", N_("Central European")}, + {SUBTITLE_ENCODING_IBM_852, FALSE, + "IBM852", N_("Central European")}, + {SUBTITLE_ENCODING_MAC_CE, FALSE, + "MAC_CE", N_("Central European")}, + {SUBTITLE_ENCODING_WINDOWS_1250, FALSE, + "WINDOWS-1250", N_("Central European")}, + + {SUBTITLE_ENCODING_GB18030, FALSE, + "GB18030", N_("Chinese Simplified")}, + {SUBTITLE_ENCODING_GB2312, FALSE, + "GB2312", N_("Chinese Simplified")}, + {SUBTITLE_ENCODING_GBK, FALSE, + "GBK", N_("Chinese Simplified")}, + {SUBTITLE_ENCODING_HZ, FALSE, + "HZ", N_("Chinese Simplified")}, + + {SUBTITLE_ENCODING_BIG5, FALSE, + "BIG5", N_("Chinese Traditional")}, + {SUBTITLE_ENCODING_BIG5_HKSCS, FALSE, + "BIG5-HKSCS", N_("Chinese Traditional")}, + {SUBTITLE_ENCODING_EUC_TW, FALSE, + "EUC-TW", N_("Chinese Traditional")}, + + {SUBTITLE_ENCODING_MAC_CROATIAN, FALSE, + "MAC_CROATIAN", N_("Croatian")}, + + {SUBTITLE_ENCODING_ISO_8859_5, FALSE, + "ISO-8859-5", N_("Cyrillic")}, + {SUBTITLE_ENCODING_IBM_855, FALSE, + "IBM855", N_("Cyrillic")}, + {SUBTITLE_ENCODING_ISO_IR_111, FALSE, + "ISO-IR-111", N_("Cyrillic")}, + {SUBTITLE_ENCODING_KOI8_R, FALSE, + "KOI8-R", N_("Cyrillic")}, + {SUBTITLE_ENCODING_MAC_CYRILLIC, FALSE, + "MAC-CYRILLIC", N_("Cyrillic")}, + {SUBTITLE_ENCODING_WINDOWS_1251, FALSE, + "WINDOWS-1251", N_("Cyrillic")}, + + {SUBTITLE_ENCODING_CP_866, FALSE, + "CP866", N_("Cyrillic/Russian")}, + + {SUBTITLE_ENCODING_MAC_UKRAINIAN, FALSE, + "MAC_UKRAINIAN", N_("Cyrillic/Ukrainian")}, + {SUBTITLE_ENCODING_KOI8_U, FALSE, + "KOI8-U", N_("Cyrillic/Ukrainian")}, + + {SUBTITLE_ENCODING_GEOSTD8, FALSE, + "GEORGIAN-PS", N_("Georgian")}, + + {SUBTITLE_ENCODING_ISO_8859_7, FALSE, + "ISO-8859-7", N_("Greek")}, + {SUBTITLE_ENCODING_MAC_GREEK, FALSE, + "MAC_GREEK", N_("Greek")}, + {SUBTITLE_ENCODING_WINDOWS_1253, FALSE, + "WINDOWS-1253", N_("Greek")}, + + {SUBTITLE_ENCODING_MAC_GUJARATI, FALSE, + "MAC_GUJARATI", N_("Gujarati")}, + + {SUBTITLE_ENCODING_MAC_GURMUKHI, FALSE, + "MAC_GURMUKHI", N_("Gurmukhi")}, + + {SUBTITLE_ENCODING_ISO_8859_8_I, FALSE, + "ISO-8859-8-I", N_("Hebrew")}, + {SUBTITLE_ENCODING_IBM_862, FALSE, + "IBM862", N_("Hebrew")}, + {SUBTITLE_ENCODING_MAC_HEBREW, FALSE, + "MAC_HEBREW", N_("Hebrew")}, + {SUBTITLE_ENCODING_WINDOWS_1255, FALSE, + "WINDOWS-1255", N_("Hebrew")}, + + {SUBTITLE_ENCODING_ISO_8859_8, FALSE, + "ISO-8859-8", N_("Hebrew Visual")}, + + {SUBTITLE_ENCODING_MAC_DEVANAGARI, FALSE, + "MAC_DEVANAGARI", N_("Hindi")}, + + {SUBTITLE_ENCODING_MAC_ICELANDIC, FALSE, + "MAC_ICELANDIC", N_("Icelandic")}, + + {SUBTITLE_ENCODING_EUC_JP, FALSE, + "EUC-JP", N_("Japanese")}, + {SUBTITLE_ENCODING_ISO_2022_JP, FALSE, + "ISO2022JP", N_("Japanese")}, + {SUBTITLE_ENCODING_SHIFT_JIS, FALSE, + "SHIFT-JIS", N_("Japanese")}, + + {SUBTITLE_ENCODING_EUC_KR, FALSE, + "EUC-KR", N_("Korean")}, + {SUBTITLE_ENCODING_ISO_2022_KR, FALSE, + "ISO2022KR", N_("Korean")}, + {SUBTITLE_ENCODING_JOHAB, FALSE, + "JOHAB", N_("Korean")}, + {SUBTITLE_ENCODING_UHC, FALSE, + "UHC", N_("Korean")}, + + {SUBTITLE_ENCODING_ISO_8859_10, FALSE, + "ISO-8859-10", N_("Nordic")}, + + {SUBTITLE_ENCODING_MAC_FARSI, FALSE, + "MAC_FARSI", N_("Persian")}, + + {SUBTITLE_ENCODING_ISO_8859_16, FALSE, + "ISO-8859-16", N_("Romanian")}, + {SUBTITLE_ENCODING_MAC_ROMANIAN, FALSE, + "MAC_ROMANIAN", N_("Romanian")}, + + {SUBTITLE_ENCODING_ISO_8859_3, FALSE, + "ISO-8859-3", N_("South European")}, + + {SUBTITLE_ENCODING_TIS_620, FALSE, + "TIS-620", N_("Thai")}, + + {SUBTITLE_ENCODING_ISO_8859_9, FALSE, + "ISO-8859-9", N_("Turkish")}, + {SUBTITLE_ENCODING_IBM_857, FALSE, + "IBM857", N_("Turkish")}, + {SUBTITLE_ENCODING_MAC_TURKISH, FALSE, + "MAC_TURKISH", N_("Turkish")}, + {SUBTITLE_ENCODING_WINDOWS_1254, FALSE, + "WINDOWS-1254", N_("Turkish")}, + + {SUBTITLE_ENCODING_UTF_7, FALSE, + "UTF-7", N_("Unicode")}, + {SUBTITLE_ENCODING_UTF_8, FALSE, + "UTF-8", N_("Unicode")}, + {SUBTITLE_ENCODING_UTF_16, FALSE, + "UTF-16", N_("Unicode")}, + {SUBTITLE_ENCODING_UCS_2, FALSE, + "UCS-2", N_("Unicode")}, + {SUBTITLE_ENCODING_UCS_4, FALSE, + "UCS-4", N_("Unicode")}, + + {SUBTITLE_ENCODING_ISO_8859_1, FALSE, + "ISO-8859-1", N_("Western")}, + {SUBTITLE_ENCODING_ISO_8859_15, FALSE, + "ISO-8859-15", N_("Western")}, + {SUBTITLE_ENCODING_IBM_850, FALSE, + "IBM850", N_("Western")}, + {SUBTITLE_ENCODING_MAC_ROMAN, FALSE, + "MAC_ROMAN", N_("Western")}, + {SUBTITLE_ENCODING_WINDOWS_1252, FALSE, + "WINDOWS-1252", N_("Western")}, + + {SUBTITLE_ENCODING_TCVN, FALSE, + "TCVN", N_("Vietnamese")}, + {SUBTITLE_ENCODING_VISCII, FALSE, + "VISCII", N_("Vietnamese")}, + {SUBTITLE_ENCODING_WINDOWS_1258, FALSE, + "WINDOWS-1258", N_("Vietnamese")} +}; + +static const SubtitleEncoding * +find_encoding_by_charset (const char *charset) +{ + int i; + + i = 1; /* skip current locale */ + while (i < SUBTITLE_ENCODING_LAST) { + if (strcasecmp (charset, encodings[i].charset) == 0) + return &encodings[i]; + + ++i; + } + + if (strcasecmp (charset, + encodings[SUBTITLE_ENCODING_CURRENT_LOCALE].charset) == 0) + return &encodings[SUBTITLE_ENCODING_CURRENT_LOCALE]; + + return NULL; +} + +static void +subtitle_encoding_init (void) +{ + int i; + gsize bytes_read, bytes_written; + gchar *converted; + gchar ascii_sample[96]; + + g_get_charset ((const char **) + &encodings[SUBTITLE_ENCODING_CURRENT_LOCALE].charset); + + g_assert (G_N_ELEMENTS (encodings) == SUBTITLE_ENCODING_LAST); + + /* Initialize the sample text with all of the printing ASCII characters + * from space (32) to the tilde (126), 95 in all. */ + for (i = 0; i < (int) sizeof (ascii_sample); i++) + ascii_sample[i] = i + 32; + + ascii_sample[sizeof (ascii_sample) - 1] = '\0'; + + i = 0; + while (i < SUBTITLE_ENCODING_LAST) { + bytes_read = 0; + bytes_written = 0; + + g_assert (encodings[i].index == i); + + /* Translate the names */ + encodings[i].name = _(encodings[i].name); + + /* Test that the encoding is a proper superset of ASCII (which naive + * apps are going to use anyway) by attempting to validate the text + * using the current encoding. This also flushes out any encodings + * which the underlying GIConv implementation can't support. + */ + converted = g_convert (ascii_sample, sizeof (ascii_sample) - 1, + encodings[i].charset, encodings[i].charset, + &bytes_read, &bytes_written, NULL); + + /* The encoding is only valid if ASCII passes through cleanly. */ + if (i == SUBTITLE_ENCODING_CURRENT_LOCALE) + encodings[i].valid = TRUE; + else + encodings[i].valid = + (bytes_read == (sizeof (ascii_sample) - 1)) && + (converted != NULL) && (strcmp (converted, ascii_sample) == 0); + +#ifdef DEBUG_ENCODINGS + if (!encodings[i].valid) { + g_print ("Rejecting encoding %s as invalid:\n", encodings[i].charset); + g_print (" input \"%s\"\n", ascii_sample); + g_print (" output \"%s\"\n\n", converted ? converted : "(null)"); + } +#endif + + /* Discard the converted string. */ + if (converted != NULL) + g_free (converted); + + ++i; + } +} + +static int +subtitle_encoding_get_index (const char *charset) +{ + const SubtitleEncoding *e; + + e = find_encoding_by_charset (charset); + if (e != NULL) + return e->index; + else + return SUBTITLE_ENCODING_CURRENT_LOCALE; +} + +static const char * +subtitle_encoding_get_charset (int index) +{ + const SubtitleEncoding *e; + + if (index >= SUBTITLE_ENCODING_LAST) + e = &encodings[SUBTITLE_ENCODING_CURRENT_LOCALE]; + else if (index < SUBTITLE_ENCODING_CURRENT_LOCALE) + e = &encodings[SUBTITLE_ENCODING_CURRENT_LOCALE]; + else if (!encodings[index].valid) + e = &encodings[SUBTITLE_ENCODING_CURRENT_LOCALE]; + else + e = &encodings[index]; + return e->charset; +} + +enum +{ + INDEX_COL, + NAME_COL +}; + +static gint +compare (GtkTreeModel * model, GtkTreeIter * a, GtkTreeIter * b, gpointer data) +{ + gchar *str_a, *str_b; + gint result; + + gtk_tree_model_get (model, a, NAME_COL, &str_a, -1); + gtk_tree_model_get (model, b, NAME_COL, &str_b, -1); + + result = strcmp (str_a, str_b); + + g_free (str_a); + g_free (str_b); + + return result; +} + +static void +is_encoding_sensitive (GtkCellLayout * cell_layout, + GtkCellRenderer * cell, + GtkTreeModel * tree_model, GtkTreeIter * iter, gpointer data) +{ + + gboolean sensitive; + + sensitive = !gtk_tree_model_iter_has_child (tree_model, iter); + g_object_set (cell, "sensitive", sensitive, NULL); +} + +static GtkTreeModel * +subtitle_encoding_create_store (void) +{ + gchar *label; + gchar *lastlang = ""; + GtkTreeIter iter, iter2; + GtkTreeStore *store; + int i; + + store = gtk_tree_store_new (2, G_TYPE_INT, G_TYPE_STRING); + + for (i = 0; i < SUBTITLE_ENCODING_LAST; i++) { + if (encodings[i].valid) { + if (strcmp (lastlang, encodings[i].name)) { + lastlang = encodings[i].name; + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, INDEX_COL, + -1, NAME_COL, lastlang, -1); + } + label = g_strdup_printf("%s (%s)", lastlang, encodings[i].charset); + gtk_tree_store_append (store, &iter2, &iter); + gtk_tree_store_set (store, &iter2, INDEX_COL, + encodings[i].index, NAME_COL, label, -1); + g_free(label); + } + } + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store), + compare, NULL, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + NAME_COL, GTK_SORT_ASCENDING); + return GTK_TREE_MODEL (store); +} + +static void +subtitle_encoding_combo_render (GtkComboBox * combo) +{ + GtkCellRenderer *renderer; + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, + "text", NAME_COL, NULL); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo), + renderer, is_encoding_sensitive, NULL, NULL); +} + +const char * +totem_subtitle_encoding_get_selected (GtkComboBox * combo) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gint index = -1; + + model = gtk_combo_box_get_model (combo); + if (gtk_combo_box_get_active_iter (combo, &iter)) { + gtk_tree_model_get (model, &iter, INDEX_COL, &index, -1); + } + if (index == -1) + return NULL; + return subtitle_encoding_get_charset (index); +} + +void +totem_subtitle_encoding_set (GtkComboBox * combo, const char *encoding) +{ + GtkTreeModel *model; + GtkTreeIter iter, iter2; + gint index, i; + + g_return_if_fail (encoding != NULL); + + model = gtk_combo_box_get_model (combo); + index = subtitle_encoding_get_index (encoding); + gtk_tree_model_get_iter_first (model, &iter); + do { + if (!gtk_tree_model_iter_has_child (model, &iter)) + continue; + if (!gtk_tree_model_iter_children (model, &iter2, &iter)) + continue; + do { + gtk_tree_model_get (model, &iter2, INDEX_COL, &i, -1); + if (i == index) + break; + } while (gtk_tree_model_iter_next (model, &iter2)); + if (i == index) + break; + } while (gtk_tree_model_iter_next (model, &iter)); + gtk_combo_box_set_active_iter (combo, &iter2); +} + +void +totem_subtitle_encoding_init (GtkComboBox *combo) +{ + GtkTreeModel *model; + subtitle_encoding_init (); + model = subtitle_encoding_create_store (); + gtk_combo_box_set_model (combo, model); + g_object_unref (model); + subtitle_encoding_combo_render (combo); +} + +/* + * vim: sw=2 ts=8 cindent noai bs=2 + */ diff --git a/trunk/src/totem-subtitle-encoding.h b/trunk/src/totem-subtitle-encoding.h new file mode 100644 index 000000000..7283f003a --- /dev/null +++ b/trunk/src/totem-subtitle-encoding.h @@ -0,0 +1,12 @@ +/* Encoding stuff */ + +#ifndef TOTEM_SUBTITLE_ENCODING_H +#define TOTEM_SUBTITLE_ENCODING_H + +#include <gtk/gtk.h> + +void totem_subtitle_encoding_init (GtkComboBox *combo); +void totem_subtitle_encoding_set (GtkComboBox *combo, const char *encoding); +const char * totem_subtitle_encoding_get_selected (GtkComboBox *combo); + +#endif /* SUBTITLE_ENCODING_H */ diff --git a/trunk/src/totem-time-label.c b/trunk/src/totem-time-label.c new file mode 100644 index 000000000..983d9d728 --- /dev/null +++ b/trunk/src/totem-time-label.c @@ -0,0 +1,106 @@ + +#include "totem-time-label.h" +#include <glib/gi18n.h> +#include "video-utils.h" + +static void totem_time_label_class_init (TotemTimeLabelClass *class); +static void totem_time_label_init (TotemTimeLabel *label); + +struct TotemTimeLabelPrivate { + gint64 time; + gint64 length; + gboolean seeking; +}; + +static GObjectClass *parent_class = NULL; + +G_DEFINE_TYPE (TotemTimeLabel, totem_time_label, GTK_TYPE_LABEL) + +static void +totem_time_label_init (TotemTimeLabel *label) +{ + char *time; + + time = totem_time_to_string (0); + gtk_label_set_text (GTK_LABEL (label), time); + g_free (time); + + label->priv = g_new0 (TotemTimeLabelPrivate, 1); + label->priv->time = 0; + label->priv->length = -1; + label->priv->seeking = FALSE; +} + +GtkWidget* +totem_time_label_new (void) +{ + TotemTimeLabel *label; + + label = g_object_new (TOTEM_TYPE_TIME_LABEL, NULL); + + return GTK_WIDGET (label); +} + +GtkWidget *totem_time_label_new_from_glade (gchar *widget_name, + gchar *string1, gchar *string2, + gint int1, gint int2) +{ + GtkWidget *widget; + + widget = totem_time_label_new (); + gtk_widget_show (widget); + + return widget; +} + +static void +totem_time_label_class_init (TotemTimeLabelClass *klass) +{ + GtkWidgetClass *widget_class; + + parent_class = g_type_class_peek_parent (klass); + + widget_class = GTK_WIDGET_CLASS (klass); +} + +void +totem_time_label_set_time (TotemTimeLabel *label, gint64 time, gint64 length) +{ + char *label_str; + + if (time / 1000 == label->priv->time / 1000 + && length / 1000 == label->priv->length / 1000) + return; + + if (length <= 0) + { + label_str = totem_time_to_string (time); + } else { + char *time_str, *length_str; + + time_str = totem_time_to_string (time); + length_str = totem_time_to_string (length); + if (label->priv->seeking == FALSE) + /* Elapsed / Total Length */ + label_str = g_strdup_printf (_("%s / %s"), time_str, length_str); + else + /* Seeking to Time / Total Length */ + label_str = g_strdup_printf (_("Seek to %s / %s"), time_str, length_str); + g_free (time_str); + g_free (length_str); + } + + gtk_label_set_text (GTK_LABEL (label), label_str); + g_free (label_str); + + label->priv->time = time; + label->priv->length = length; +} + +void +totem_time_label_set_seeking (TotemTimeLabel *label, gboolean seeking) +{ + g_return_if_fail (TOTEM_IS_TIME_LABEL (label)); + + label->priv->seeking = seeking; +} diff --git a/trunk/src/totem-time-label.h b/trunk/src/totem-time-label.h new file mode 100644 index 000000000..8cb62089f --- /dev/null +++ b/trunk/src/totem-time-label.h @@ -0,0 +1,37 @@ + +#ifndef TOTEM_TIME_LABEL_H +#define TOTEM_TIME_LABEL_H + +#include <gtk/gtklabel.h> + +#define TOTEM_TYPE_TIME_LABEL (totem_time_label_get_type ()) +#define TOTEM_TIME_LABEL(obj) (GTK_CHECK_CAST ((obj), TOTEM_TYPE_TIME_LABEL, TotemTimeLabel)) +#define TOTEM_TIME_LABEL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_TIME_LABEL, TotemTimeLabelClass)) +#define TOTEM_IS_TIME_LABEL(obj) (GTK_CHECK_TYPE ((obj), TOTEM_TYPE_TIME_LABEL)) +#define TOTEM_IS_TIME_LABEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_TIME_LABEL)) + +typedef struct TotemTimeLabel TotemTimeLabel; +typedef struct TotemTimeLabelClass TotemTimeLabelClass; +typedef struct TotemTimeLabelPrivate TotemTimeLabelPrivate; + +struct TotemTimeLabel { + GtkLabel parent; + TotemTimeLabelPrivate *priv; +}; + +struct TotemTimeLabelClass { + GtkLabelClass parent_class; +}; + +GtkType totem_time_label_get_type (void); +GtkWidget *totem_time_label_new (void); +void totem_time_label_set_time (TotemTimeLabel *label, + gint64 time, gint64 length); +void totem_time_label_set_seeking (TotemTimeLabel *label, + gboolean seeking); +GtkWidget *totem_time_label_new_from_glade + (gchar *widget_name, + gchar *string1, gchar *string2, + gint int1, gint int2); + +#endif /* TOTEM_TIME_LABEL_H */ diff --git a/trunk/src/totem-uri.c b/trunk/src/totem-uri.c new file mode 100644 index 000000000..e64270d95 --- /dev/null +++ b/trunk/src/totem-uri.c @@ -0,0 +1,324 @@ +/* totem-uri.c + + Copyright (C) 2004 Bastien Nocera + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <string.h> +#include <libgnomevfs/gnome-vfs-utils.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "totem-mime-types.h" +#include "totem-uri.h" +#include "totem-private.h" + +static GtkFileFilter *filter_all; +static GtkFileFilter *filter_supported; + +gboolean +totem_playing_dvd (const char *uri) +{ + if (uri == NULL) + return FALSE; + + return g_str_has_prefix (uri, "dvd:/"); +} + +gboolean +totem_is_media (const char *uri) +{ + if (uri == NULL) + return FALSE; + + if (g_str_has_prefix (uri, "cdda:") != FALSE) + return TRUE; + if (g_str_has_prefix (uri, "dvd:") != FALSE) + return TRUE; + if (g_str_has_prefix (uri, "vcd:") != FALSE) + return TRUE; + if (g_str_has_prefix (uri, "cd:") != FALSE) + return TRUE; + + return FALSE; +} + +gboolean +totem_is_block_device (const char *uri) +{ + struct stat buf; + char *local; + + if (uri == NULL) + return FALSE; + + if (g_str_has_prefix (uri, "file:") == FALSE) + return FALSE; + local = g_filename_from_uri (uri, NULL, NULL); + if (local == NULL) + return FALSE; + if (stat (local, &buf) != 0) { + g_free (local); + return FALSE; + } + g_free (local); + + return (S_ISBLK (buf.st_mode)); +} + +char* +totem_create_full_path (const char *path) +{ + char *retval, *curdir, *curdir_withslash, *escaped; + + g_return_val_if_fail (path != NULL, NULL); + + if (strstr (path, "://") != NULL) + return g_strdup (path); + if (totem_is_media (path) != FALSE) + return g_strdup (path); + + if (path[0] == '/') + { + escaped = gnome_vfs_escape_path_string (path); + + retval = g_strdup_printf ("file://%s", escaped); + g_free (escaped); + return retval; + } + + curdir = g_get_current_dir (); + escaped = gnome_vfs_escape_path_string (curdir); + curdir_withslash = g_strdup_printf ("file://%s%s", + escaped, G_DIR_SEPARATOR_S); + g_free (escaped); + g_free (curdir); + + escaped = gnome_vfs_escape_path_string (path); + retval = gnome_vfs_uri_make_full_from_relative + (curdir_withslash, escaped); + g_free (curdir_withslash); + g_free (escaped); + + return retval; +} + +static void +totem_action_on_unmount (GnomeVFSVolumeMonitor *vfsvolumemonitor, + GnomeVFSVolume *volume, Totem *totem) +{ + totem_playlist_clear_with_gnome_vfs_volume (totem->playlist, volume); +} + +void +totem_setup_file_monitoring (Totem *totem) +{ + totem->monitor = gnome_vfs_get_volume_monitor (); + + g_signal_connect (G_OBJECT (totem->monitor), + "volume_pre_unmount", + G_CALLBACK (totem_action_on_unmount), + totem); +} + +/* List from xine-lib's demux_sputext.c */ +static const char *subtitle_ext[] = { + "asc", + "txt", + "sub", + "srt", + "smi", + "ssa" +}; + +char * +totem_uri_get_subtitle_uri (const char *uri) +{ + char *suffix, *subtitle; + guint len, i; + GnomeVFSURI *vfsuri; + + if (g_str_has_prefix (uri, "http") != FALSE) { + return NULL; + } + + /* Does gnome-vfs support that scheme? */ + vfsuri = gnome_vfs_uri_new (uri); + if (vfsuri == NULL) + return NULL; + gnome_vfs_uri_unref (vfsuri); + + if (strstr (uri, "#subtitle:") != NULL) { + return NULL; + } + + len = strlen (uri); + if (uri[len-4] != '.') { + return NULL; + } + + subtitle = g_strdup (uri); + suffix = subtitle + len - 4; + for (i = 0; i < G_N_ELEMENTS(subtitle_ext) ; i++) { + memcpy (suffix + 1, subtitle_ext[i], 3); + + vfsuri = gnome_vfs_uri_new (subtitle); + if (vfsuri != NULL) { + if (gnome_vfs_uri_exists (vfsuri)) { + gnome_vfs_uri_unref (vfsuri); + return subtitle; + } + gnome_vfs_uri_unref (vfsuri); + } + } + g_free (subtitle); + return NULL; +} + +char* +totem_uri_escape_for_display (const char *uri) +{ + char *disp, *tmp; + + disp = gnome_vfs_unescape_string_for_display (uri); + /* If we don't have UTF-8, try to convert */ + if (g_utf8_validate (disp, -1, NULL) != FALSE) + return disp; + + /* If we don't have UTF-8, try to convert */ + tmp = g_locale_to_utf8 (disp, -1, NULL, NULL, NULL); + /* If we couldn't convert using the current codeset, try + * another one */ + if (tmp != NULL) { + g_free (disp); + return tmp; + } + + tmp = g_convert (disp, -1, "UTF-8", "ISO8859-1", NULL, NULL, NULL); + if (tmp != NULL) { + g_free (disp); + return tmp; + } + + return g_strdup (uri); +} + +void +totem_setup_file_filters (void) +{ + guint i; + + filter_all = gtk_file_filter_new (); + gtk_file_filter_set_name (filter_all, _("All files")); + gtk_file_filter_add_pattern (filter_all, "*"); + g_object_ref (filter_all); + + filter_supported = gtk_file_filter_new (); + gtk_file_filter_set_name (filter_supported, + _("Supported files")); + for (i = 0; i < G_N_ELEMENTS (mime_types); i++) { + gtk_file_filter_add_mime_type (filter_supported, mime_types[i]); + } + + /* Add the special Disc-as-files formats */ + gtk_file_filter_add_mime_type (filter_supported, "application/x-cd-image"); + gtk_file_filter_add_mime_type (filter_supported, "application/x-cue"); + + g_object_ref (filter_supported); +} + +void +totem_destroy_file_filters (void) +{ + g_object_unref (filter_all); + g_object_unref (filter_supported); +} + +GSList * +totem_add_files (GtkWindow *parent, const char *path) +{ + GtkWidget *fs; + int response; + GSList *filenames; + char *mrl, *new_path; + GConfClient *conf; + + fs = gtk_file_chooser_dialog_new (_("Select Movies or Playlists"), + parent, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (fs), filter_all); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (fs), filter_supported); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (fs), filter_supported); + gtk_dialog_set_default_response (GTK_DIALOG (fs), GTK_RESPONSE_ACCEPT); + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (fs), TRUE); + gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (fs), FALSE); + + conf = gconf_client_get_default (); + if (path != NULL) { + gtk_file_chooser_set_current_folder_uri + (GTK_FILE_CHOOSER (fs), path); + } else { + new_path = gconf_client_get_string (conf, "/apps/totem/open_path", NULL); + if (new_path != NULL && new_path != '\0') { + gtk_file_chooser_set_current_folder_uri + (GTK_FILE_CHOOSER (fs), new_path); + } + g_free (new_path); + } + + response = gtk_dialog_run (GTK_DIALOG (fs)); + gtk_widget_hide (fs); + while (gtk_events_pending()) + gtk_main_iteration(); + + if (response != GTK_RESPONSE_ACCEPT) { + gtk_widget_destroy (fs); + g_object_unref (conf); + return NULL; + } + + filenames = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (fs)); + if (filenames == NULL) { + gtk_widget_destroy (fs); + g_object_unref (conf); + return NULL; + } + + mrl = filenames->data; + if (mrl != NULL) { + new_path = g_path_get_dirname (mrl); + gconf_client_set_string (conf, "/apps/totem/open_path", + new_path, NULL); + g_free (new_path); + } + + gtk_widget_destroy (fs); + g_object_unref (conf); + + return filenames; +} + diff --git a/trunk/src/totem-uri.h b/trunk/src/totem-uri.h new file mode 100644 index 000000000..a8d64c6e2 --- /dev/null +++ b/trunk/src/totem-uri.h @@ -0,0 +1,45 @@ +/* totem-uri.h + + Copyright (C) 2004 Bastien Nocera <hadess@hadess.net> + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Bastien Nocera <hadess@hadess.net> + */ + +#ifndef TOTEM_URI_H +#define TOTEM_URI_H + +#include "totem.h" +#include <gtk/gtkwindow.h> + +G_BEGIN_DECLS + +char* totem_create_full_path (const char *path); +gboolean totem_is_media (const char *uri); +gboolean totem_playing_dvd (const char *uri); +gboolean totem_is_block_device (const char *uri); +void totem_setup_file_monitoring (Totem *totem); +void totem_setup_file_filters (void); +void totem_destroy_file_filters (void); +char* totem_uri_get_subtitle_uri (const char *uri); +char* totem_uri_escape_for_display (const char *uri); +GSList* totem_add_files (GtkWindow *parent, + const char *path); + +G_END_DECLS + +#endif /* TOTEM_URI_H */ diff --git a/trunk/src/totem-video-indexer.c b/trunk/src/totem-video-indexer.c new file mode 100644 index 000000000..4299988f8 --- /dev/null +++ b/trunk/src/totem-video-indexer.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2006 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#ifndef HAVE_GTK_ONLY +#include <gnome.h> +#endif + +#include <bacon-video-widget.h> +#include <glib/gthread.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <libgnomevfs/gnome-vfs-utils.h> +#include <libgnomevfs/gnome-vfs-init.h> + +#include "totem-mime-types.h" + +static gboolean show_mimetype = FALSE; +static char **filenames = NULL; + +static void +print_mimetypes (void) +{ + guint i; + + for (i =0; i < G_N_ELEMENTS (mime_types); i++) { + g_print ("%s\n", mime_types[i]); + } +} + +static const char * +boolean_to_string (gboolean data) +{ + return (data ? "True" : "False"); +} + +static void +totem_print_string (BaconVideoWidget *bvw, const char *key, BaconVideoWidgetMetadataType id) +{ + GValue value = { 0, }; + const char *str; + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + id, &value); + str = g_value_get_string (&value); + if (str != NULL) { + g_print ("%s=%s\n", key, str); + } +} + +static void +totem_print_int (BaconVideoWidget *bvw, const char *key, BaconVideoWidgetMetadataType id) +{ + GValue value = { 0, }; + int num; + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + id, &value); + num = g_value_get_int (&value); + if (num != 0) { + g_print ("%s=%d\n", key, num); + } +} + +static void +on_got_metadata_event (BaconVideoWidget *bvw, gpointer data) +{ + GValue value = { 0, }; + gboolean has_type; + + totem_print_string (bvw, "TOTEM_INFO_TITLE", BVW_INFO_TITLE); + totem_print_string (bvw, "TOTEM_INFO_ARTIST", BVW_INFO_ARTIST); + totem_print_string (bvw, "TOTEM_INFO_YEAR", BVW_INFO_YEAR); + totem_print_string (bvw, "TOTEM_INFO_ALBUM", BVW_INFO_ALBUM); + + totem_print_int (bvw, "TOTEM_INFO_DURATION", BVW_INFO_DURATION); + totem_print_int (bvw, "TOTEM_INFO_TRACK_NUMBER", BVW_INFO_TRACK_NUMBER); + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_HAS_VIDEO, &value); + has_type = g_value_get_boolean (&value); + g_print ("TOTEM_INFO_HAS_VIDEO=%s\n", boolean_to_string (has_type)); + g_value_unset (&value); + + if (has_type) { + totem_print_int (bvw, "TOTEM_INFO_VIDEO_WIDTH", BVW_INFO_DIMENSION_X); + totem_print_int (bvw, "TOTEM_INFO_VIDEO_HEIGHT", BVW_INFO_DIMENSION_Y); + totem_print_string (bvw, "TOTEM_INFO_VIDEO_CODEC", BVW_INFO_VIDEO_CODEC); + totem_print_int (bvw, "TOTEM_INFO_FPS", BVW_INFO_FPS); + totem_print_int (bvw, "TOTEM_INFO_VIDEO_BITRATE", BVW_INFO_VIDEO_BITRATE); + } + + bacon_video_widget_get_metadata (BACON_VIDEO_WIDGET (bvw), + BVW_INFO_HAS_AUDIO, &value); + has_type = g_value_get_boolean (&value); + g_print ("TOTEM_INFO_HAS_AUDIO=%s\n", boolean_to_string (has_type)); + g_value_unset (&value); + + if (has_type) { + totem_print_int (bvw, "TOTEM_INFO_AUDIO_BITRATE", BVW_INFO_AUDIO_BITRATE); + totem_print_string (bvw, "TOTEM_INFO_AUDIO_CODEC", BVW_INFO_AUDIO_CODEC); + totem_print_int (bvw, "TOTEM_INFO_AUDIO_SAMPLE_RATE", BVW_INFO_AUDIO_SAMPLE_RATE); + totem_print_string (bvw, "TOTEM_INFO_AUDIO_CHANNELS", BVW_INFO_AUDIO_CHANNELS); + } + bacon_video_widget_close (bvw); + exit (0); +} + +static const GOptionEntry entries[] = { + {"mimetype", 'm', 0, G_OPTION_ARG_NONE, &show_mimetype, "List the supported mime-types", NULL}, + {G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, "Movies to index", NULL}, + {NULL} +}; + +int main (int argc, char **argv) +{ + GOptionGroup *options; + GOptionContext *context; + GtkWidget *widget; + BaconVideoWidget *bvw; + GError *error = NULL; + const char *path; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_thread_init (NULL); + gdk_threads_init (); + context = g_option_context_new ("Index movies or songs"); + options = bacon_video_widget_get_option_group (); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, options); + g_type_init (); + +#ifndef HAVE_GTK_ONLY + gnome_authentication_manager_init (); +#endif + + gnome_vfs_init (); + if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) { + g_print ("couldn't parse command-line options: %s\n", error->message); + g_error_free (error); + return 1; + } + + if (show_mimetype == TRUE) { + print_mimetypes (); + return 0; + } else if (filenames == NULL || g_strv_length (filenames) != 1) { + g_print ("Expects exactly one URI\n"); + return 1; + } + + widget = bacon_video_widget_new (-1, -1, BVW_USE_TYPE_METADATA, &error); + if (widget == NULL) { + g_print ("error creating the video widget: %s\n", error->message); + g_error_free (error); + return 1; + } + bvw = BACON_VIDEO_WIDGET (widget); + g_signal_connect (G_OBJECT (bvw), "got-metadata", + G_CALLBACK (on_got_metadata_event), + NULL); + + path = filenames[0]; + if (bacon_video_widget_open (bvw, path, &error) == FALSE) { + g_print ("Can't open %s: %s\n", path, error->message); + return 1; + } + if (bacon_video_widget_play (bvw, &error) == FALSE) { + g_print ("Can't play %s: %s\n", path, error->message); + return 1; + } + + gtk_main (); + + return 0; +} + diff --git a/trunk/src/totem-video-thumbnailer.c b/trunk/src/totem-video-thumbnailer.c new file mode 100644 index 000000000..3ffc241d0 --- /dev/null +++ b/trunk/src/totem-video-thumbnailer.c @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2003,2004 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add exception clause. + * See license_change file for details. + * + */ + +#include "config.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#ifndef HAVE_GTK_ONLY +#include <libgnomeui/gnome-authentication-manager.h> +#endif +#include <libgnomevfs/gnome-vfs-init.h> +#include "bacon-video-widget.h" + +/* #define THUMB_DEBUG */ + +#ifdef G_HAVE_ISO_VARARGS +#define PROGRESS_DEBUG(...) { if (verbose != FALSE) g_message (__VA_ARGS__); } +#elif defined(G_HAVE_GNUC_VARARGS) +#define PROGRESS_DEBUG(format...) { if (verbose != FALSE) g_message (format); } +#endif + +#define MIN_LEN_FOR_SEEK 25000 +#define HALF_SECOND G_USEC_PER_SEC * .5 +#define BORING_IMAGE_VARIANCE 256.0 /* Tweak this if necessary */ + +gboolean finished = FALSE; +static gboolean jpeg_output = FALSE; +static gboolean output_size = 128; +static gboolean time_limit = TRUE; +static gboolean verbose = FALSE; +static char **filenames = NULL; + +#ifdef THUMB_DEBUG +static void +show_pixbuf (GdkPixbuf *pix) +{ + GtkWidget *win, *img; + + img = gtk_image_new_from_pixbuf (pix); + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_container_add (GTK_CONTAINER (win), img); + gtk_widget_show_all (win); + + /* Display and crash baby crash */ + gtk_main (); +} +#endif + +static GdkPixbuf * +add_holes_to_pixbuf_small (GdkPixbuf *pixbuf, int width, int height) +{ + GdkPixbuf *holes, *tmp, *target; + char *filename; + int i; + + filename = g_build_filename (DATADIR, "totem", "filmholes.png", NULL); + holes = gdk_pixbuf_new_from_file (filename, NULL); + g_free (filename); + + if (holes == NULL) + { + g_object_ref (pixbuf); + return pixbuf; + } + + g_assert (gdk_pixbuf_get_has_alpha (pixbuf) == FALSE); + g_assert (gdk_pixbuf_get_has_alpha (holes) != FALSE); + target = g_object_ref (pixbuf); + + for (i = 0; i < height; i += gdk_pixbuf_get_height (holes)) + { + gdk_pixbuf_composite (holes, target, 0, i, + MIN (width, gdk_pixbuf_get_width (holes)), + MIN (height-i, gdk_pixbuf_get_height (holes)), + 0, i, 1, 1, GDK_INTERP_NEAREST, 255); + } + + tmp = gdk_pixbuf_flip (holes, FALSE); + g_object_unref (holes); + holes = tmp; + + for (i = 0; i < height; i += gdk_pixbuf_get_height (holes)) + { + gdk_pixbuf_composite (holes, target, + width - gdk_pixbuf_get_width (holes), i, + MIN (width, gdk_pixbuf_get_width (holes)), + MIN (height-i, gdk_pixbuf_get_height (holes)), + width - gdk_pixbuf_get_width (holes), i, + 1, 1, GDK_INTERP_NEAREST, 255); + } + + g_object_unref (holes); + + return target; +} + +static GdkPixbuf * +add_holes_to_pixbuf_large (GdkPixbuf *pixbuf, int size) +{ + char *filename; + int lh, lw, rh, rw, i; + GdkPixbuf *left, *right, *small; + int canvas_w, canvas_h; + int d_height, d_width; + double ratio; + + filename = g_build_filename (DATADIR, "totem", + "filmholes-big-left.png", NULL); + left = gdk_pixbuf_new_from_file (filename, NULL); + g_free (filename); + + if (left == NULL) + { + g_object_ref (pixbuf); + return pixbuf; + } + + filename = g_build_filename (DATADIR, "totem", + "filmholes-big-right.png", NULL); + right = gdk_pixbuf_new_from_file (filename, NULL); + g_free (filename); + + if (right == NULL) + { + g_object_unref (left); + g_object_ref (pixbuf); + return pixbuf; + } + + lh = gdk_pixbuf_get_height (left); + lw = gdk_pixbuf_get_width (left); + rh = gdk_pixbuf_get_height (right); + rw = gdk_pixbuf_get_width (right); + g_assert (lh == rh); + g_assert (lw == rw); + + { + int height, width; + + height = gdk_pixbuf_get_height (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + + if (width > height) + { + d_width = size - lw - lw; + d_height = d_width * height / width; + } else { + d_height = size - lw -lw; + d_width = d_height * width / height; + } + + canvas_h = d_height; + canvas_w = d_width + 2 * lw; + } + + small = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, + canvas_w, canvas_h); + gdk_pixbuf_fill (small, 0x000000ff); + ratio = ((double)d_width / (double) gdk_pixbuf_get_width (pixbuf)); + + gdk_pixbuf_scale (pixbuf, small, lw, 0, + d_width, d_height, + lw, 0, ratio, ratio, GDK_INTERP_NEAREST); + + /* Left side holes */ + for (i = 0; i < canvas_h; i += lh) + { + gdk_pixbuf_composite (left, small, 0, i, + MIN (canvas_w, lw), + MIN (canvas_h - i, lh), + 0, i, 1, 1, GDK_INTERP_NEAREST, 255); + } + + /* Right side holes */ + for (i = 0; i < canvas_h; i += rh) + { + gdk_pixbuf_composite (right, small, + canvas_w - rw, i, + MIN (canvas_w, rw), + MIN (canvas_h - i, rh), + canvas_w - rw, i, + 1, 1, GDK_INTERP_NEAREST, 255); + } + + /* TODO Add a one pixel border of 0x33333300 all around */ + + return small; +} + +/* This function attempts to detect images that are mostly solid images + * It does this by calculating the statistical variance of the + * black-and-white image */ +static gboolean +is_image_interesting (GdkPixbuf *pixbuf) +{ + /* We're gonna assume 8-bit samples. If anyone uses anything different, + * it doesn't really matter cause it's gonna be ugly anyways */ + int rowstride = gdk_pixbuf_get_rowstride(pixbuf); + int height = gdk_pixbuf_get_height(pixbuf); + guchar* buffer = gdk_pixbuf_get_pixels(pixbuf); + int num_samples = (rowstride * height); + int i; + float x_bar = 0.0f; + float variance = 0.0f; + + /* FIXME: If this proves to be a performance issue, this function + * can be modified to perhaps only check 3 rows. I doubt this'll + * be a problem though. */ + + /* Iterate through the image to calculate x-bar */ + for (i = 0; i < num_samples; i++) { + x_bar += (float)buffer[i]; + } + x_bar /= ((float)num_samples); + + /* Calculate the variance */ + for (i = 0; i < num_samples; i++) { + float tmp = ((float)buffer[i] - x_bar); + variance += tmp * tmp; + } + variance /= ((float)(num_samples - 1)); + + return (variance > BORING_IMAGE_VARIANCE); +} + +static void +save_pixbuf (GdkPixbuf *pixbuf, const char *path, + const char *video_path, int size, gboolean is_still) +{ + int width, height; + GdkPixbuf *small, *with_holes; + GError *err = NULL; + char *a_width, *a_height; + + height = gdk_pixbuf_get_height (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + + if (size <= 256) + { + int d_width, d_height; + + height = gdk_pixbuf_get_height (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + + if (width > height) + { + d_width = size; + d_height = size * height / width; + } else { + d_height = size; + d_width = size * width / height; + } + + small = gdk_pixbuf_scale_simple (pixbuf, d_width, d_height, + GDK_INTERP_TILES); + + if (is_still == FALSE) { + with_holes = add_holes_to_pixbuf_small (small, + d_width, d_height); + g_return_if_fail (with_holes != NULL); + g_object_unref (small); + } else { + with_holes = small; + } + } else { + with_holes = add_holes_to_pixbuf_large (pixbuf, size); + g_return_if_fail (with_holes != NULL); + } + + a_width = g_strdup_printf ("%d", width); + a_height = g_strdup_printf ("%d", height); + + if (gdk_pixbuf_save (with_holes, path, + jpeg_output ? "jpeg" : "png", &err, + "tEXt::Thumb::Image::Width", a_width, + "tEXt::Thumb::Image::Height", a_height, + NULL) == FALSE) + { + g_free (a_width); + g_free (a_height); + + if (err != NULL) + { + g_print ("totem-video-thumbnailer couln't write the thumbnail '%s' for video '%s': %s\n", path, video_path, err->message); + g_error_free (err); + } else { + g_print ("totem-video-thumbnailer couln't write the thumbnail '%s' for video '%s'\n", path, video_path); + } + + g_object_unref (with_holes); + return; + } + +#ifdef THUMB_DEBUG + show_pixbuf (with_holes); +#endif + + g_object_unref (with_holes); +} + +static gpointer +time_monitor (gpointer data) +{ + g_usleep (30 * G_USEC_PER_SEC); + + if (finished != FALSE) + g_thread_exit (NULL); + + g_print ("totem-video-thumbnailer couln't thumbnail file: '%s'\n" + "Reason: Took too much time to thumbnail.\n", + (const char *) data); + exit (0); +} + +static const GOptionEntry entries[] = { + { "jpeg", 'j', 0, G_OPTION_ARG_NONE, &jpeg_output, "Output the thumbnail as a JPEG instead of PNG", NULL }, + { "size", 's', 0, G_OPTION_ARG_INT, &output_size, "Size of the thumbnail in pixels", NULL }, + { "no-limit", 'l', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &time_limit, "Don't limit the thumbnailing time to 30 seconds", NULL }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Output debug information", NULL }, + { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, "Movies to index", NULL }, + { NULL } +}; + +int main (int argc, char *argv[]) +{ + GOptionGroup *options; + GOptionContext *context; + GError *err = NULL; + BaconVideoWidget *bvw; + GdkPixbuf *pixbuf; + int length; + char *input, *output; + + const float frame_locations[] = { + 1.0 / 3.0, + 2.0 / 3.0, + 0.1, + 0.9, + 0.5 + }; + guint current; + +#ifdef G_OS_UNIX + nice (20); +#endif + + g_thread_init (NULL); + + context = g_option_context_new ("Thumbnail movies"); + options = bacon_video_widget_get_option_group (); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, options); +#ifndef THUMB_DEBUG + g_type_init (); +#else + gtk_init (&argc, &argv); +#endif + +#ifndef HAVE_GTK_ONLY + gnome_authentication_manager_init (); +#endif + + gnome_vfs_init (); + if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) { + g_print ("couldn't parse command-line options: %s\n", err->message); + g_error_free (err); + return 1; + } + + if (filenames == NULL || g_strv_length (filenames) != 2) { + g_print ("Expects an input and an output file\n"); + return 1; + } + input = filenames[0]; + output = filenames[1]; + + PROGRESS_DEBUG("Initialised libraries, about to create video widget\n"); + + bvw = BACON_VIDEO_WIDGET (bacon_video_widget_new (-1, -1, BVW_USE_TYPE_CAPTURE, &err)); + if (err != NULL) + { + g_print ("totem-video-thumbnailer couln't create the video " + "widget.\nReason: %s.\n", err->message); + g_error_free (err); + exit (1); + } + + PROGRESS_DEBUG("Video widget created\n"); + + if (time_limit != FALSE) + g_thread_create (time_monitor, (gpointer) input, FALSE, NULL); + + PROGRESS_DEBUG("About to open video file\n"); + + if (bacon_video_widget_open (bvw, input, &err) == FALSE) + { + g_print ("totem-video-thumbnailer couln't open file '%s'\n" + "Reason: %s.\n", + input, err->message); + g_error_free (err); + exit (1); + } + + PROGRESS_DEBUG("Opened video file: '%s'\n", input); + PROGRESS_DEBUG("About to play file\n"); + + bacon_video_widget_play (bvw, &err); + if (err != NULL) + { + g_print ("totem-video-thumbnailer couln't play file: '%s'\n" + "Reason: %s.\n", input, err->message); + g_error_free (err); + exit (1); + } + + PROGRESS_DEBUG("Started playing file\n"); + + /* Test at multiple points in the file to see if we can get an + * interesting frame */ + for (current = 0; current < G_N_ELEMENTS(frame_locations); current++) + { + length = bacon_video_widget_get_stream_length (bvw); + if (length > MIN_LEN_FOR_SEEK) + { + PROGRESS_DEBUG("About to seek to %f\n", frame_locations[current]); + if (bacon_video_widget_seek + (bvw, frame_locations[current], NULL) == FALSE) + { + bacon_video_widget_play (bvw, NULL); + } + } + + if (bacon_video_widget_can_get_frames (bvw, &err) == FALSE) + { + g_print ("totem-video-thumbnailer: '%s' isn't thumbnailable\n" + "Reason: %s\n", + input, err ? err->message : "programming error"); + bacon_video_widget_close (bvw); + gtk_widget_destroy (GTK_WIDGET (bvw)); + g_error_free (err); + + exit (1); + } + + /* Pull the frame, if it's interesting we bail early */ + PROGRESS_DEBUG("About to get frame for iter %d\n", current); + pixbuf = bacon_video_widget_get_current_frame (bvw); + if (pixbuf != NULL && is_image_interesting (pixbuf) != FALSE) { + PROGRESS_DEBUG("Frame for iter %d is interesting\n", current); + break; + } + + /* If we get to the end of this loop, we'll end up using + * the last image we pulled */ + if(current + 1 < G_N_ELEMENTS(frame_locations)) { + if (pixbuf != NULL) + g_object_unref (pixbuf); + } + PROGRESS_DEBUG("Frame for iter %d was not interesting\n", current); + } + + /* Cleanup */ + bacon_video_widget_close (bvw); + finished = TRUE; + gtk_widget_destroy (GTK_WIDGET (bvw)); + + if (pixbuf == NULL) + { + g_print ("totem-video-thumbnailer couln't get a picture from " + "'%s'\n", input); + exit (1); + } + + PROGRESS_DEBUG("Saving captured screenshot\n"); + save_pixbuf (pixbuf, output, input, output_size, FALSE); + g_object_unref (pixbuf); + + return 0; +} + diff --git a/trunk/src/totem.c b/trunk/src/totem.c new file mode 100644 index 000000000..2dd904bc7 --- /dev/null +++ b/trunk/src/totem.c @@ -0,0 +1,3566 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2006 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include "config.h" + +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <stdlib.h> + +#ifndef HAVE_GTK_ONLY +#include <gnome.h> +#include "totem-gromit.h" +#endif /* !HAVE_GTK_ONLY */ + +#include <string.h> + +#ifdef GDK_WINDOWING_X11 +/* X11 headers */ +#include <gdk/gdkx.h> +#include <X11/Xlib.h> +#ifdef HAVE_XFREE +#include <X11/XF86keysym.h> +#endif +#endif + +#include "bacon-video-widget.h" +#include "bacon-video-widget-properties.h" +#include "totem-statusbar.h" +#include "totem-time-label.h" +#include "totem-session.h" +#include "totem-screenshot.h" +#include "totem-sidebar.h" +#include "totem-menu.h" +#include "totem-missing-plugins.h" +#include "totem-options.h" +#include "totem-uri.h" +#include "totem-interface.h" +#include "bacon-volume.h" +#include "video-utils.h" + +#include "totem.h" +#include "totem-private.h" +#include "totem-preferences.h" +#include "totem-disc.h" + +#include "debug.h" + +#define REWIND_OR_PREVIOUS 4000 + +#define SEEK_FORWARD_SHORT_OFFSET 15 +#define SEEK_BACKWARD_SHORT_OFFSET -5 + +#define SEEK_FORWARD_LONG_OFFSET 10*60 +#define SEEK_BACKWARD_LONG_OFFSET -3*60 + +#define ZOOM_UPPER 200 +#define ZOOM_RESET 100 +#define ZOOM_LOWER 10 +#define ZOOM_DISABLE (ZOOM_LOWER - 1) +#define ZOOM_ENABLE (ZOOM_UPPER + 1) + +#define FULLSCREEN_POPUP_TIMEOUT 5 * 1000 + +#define BVW_VBOX_BORDER_WIDTH 1 + +static const GtkTargetEntry target_table[] = { + { "text/uri-list", 0, 0 }, + { "_NETSCAPE_URL", 0, 1 }, +}; + +static const GtkTargetEntry source_table[] = { + { "text/uri-list", 0, 0 }, +}; + +static gboolean totem_action_open_files (Totem *totem, char **list); +static gboolean totem_action_open_files_list (Totem *totem, GSList *list); +static void update_fullscreen_size (Totem *totem); +static gboolean popup_hide (Totem *totem); +static void update_buttons (Totem *totem); +static void update_media_menu_items (Totem *totem); +static void update_seekable (Totem *totem, gboolean force_false); +static void playlist_changed_cb (GtkWidget *playlist, Totem *totem); +static void play_pause_set_label (Totem *totem, TotemStates state); +static gboolean on_video_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, Totem *totem); +static void popup_timeout_remove (Totem *totem); +static gint totem_compare_recent_stream_items (GtkRecentInfo *a, GtkRecentInfo *b); + +static void +long_action (void) +{ + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +void +totem_action_error (const char *title, const char *reason, Totem *totem) +{ + totem_interface_error (title, reason, + GTK_WINDOW (totem->win)); +} + +static void +totem_action_error_and_exit (const char *title, + const char *reason, Totem *totem) +{ + totem_interface_error_blocking (title, reason, + GTK_WINDOW (totem->win)); + totem_action_exit (totem); +} + +static void +totem_action_save_size (Totem *totem) +{ + GtkWidget *item; + + if (totem->bvw == NULL) + return; + + if (totem_is_fullscreen (totem) != FALSE) + return; + + /* Save the size of the video widget */ + item = glade_xml_get_widget (totem->xml, "tmw_main_pane"); + gtk_window_get_size (GTK_WINDOW (totem->win), &totem->window_w, + &totem->window_h); + totem->sidebar_w = totem->window_w + - gtk_paned_get_position (GTK_PANED (item)); +} + +static void +totem_action_save_state (Totem *totem) +{ + GKeyFile *keyfile; + char *contents, *filename; + const char *page_id; + + keyfile = g_key_file_new (); + g_key_file_set_integer (keyfile, "State", + "window_w", totem->window_w); + g_key_file_set_integer (keyfile, "State", + "window_h", totem->window_h); + g_key_file_set_boolean (keyfile, "State", + "show_sidebar", totem_sidebar_is_visible (totem)); + g_key_file_set_boolean (keyfile, "State", + "maximised", totem->maximised); + g_key_file_set_integer (keyfile, "State", + "sidebar_w", totem->sidebar_w); + + page_id = totem_sidebar_get_current_page (totem); + g_key_file_set_string (keyfile, "State", + "sidebar_page", page_id); + + contents = g_key_file_to_data (keyfile, NULL, NULL); + g_key_file_free (keyfile); + filename = g_build_filename (g_get_home_dir (), ".gnome2", "totem", NULL); + g_file_set_contents (filename, contents, -1, NULL); + + g_free (filename); + g_free (contents); +} + +void +totem_action_exit (Totem *totem) +{ + GdkDisplay *display = NULL; + + if (totem != NULL) + popup_timeout_remove (totem); + + if (gtk_main_level () > 0) + gtk_main_quit (); + + if (totem == NULL) + exit (0); + +#ifdef HAVE_REMOTE + if (totem->remote != NULL) { + g_object_unref (G_OBJECT (totem->remote)); + } +#endif /* HAVE_REMOTE */ + + if (totem->win != NULL) { + gtk_widget_hide (totem->win); + display = gtk_widget_get_display (totem->win); + } + + if (totem->prefs != NULL) { + gtk_widget_hide (totem->prefs); + } + +#ifndef HAVE_GTK_ONLY + totem_gromit_clear (TRUE); +#endif /* !HAVE_GTK_ONLY */ + + if (display != NULL) + gdk_display_sync (display); + + if (totem->bvw) { + //FIXME move the volume to the static file? + gconf_client_set_int (totem->gc, + GCONF_PREFIX"/volume", + bacon_video_widget_get_volume (totem->bvw), + NULL); + totem_action_save_size (totem); + } + + bacon_message_connection_free (totem->conn); + totem_action_save_state (totem); + + totem_action_fullscreen (totem, FALSE); + + totem_sublang_exit (totem); + totem_destroy_file_filters (); + + if (totem->gc) + g_object_unref (G_OBJECT (totem->gc)); + + if (totem->win) + gtk_widget_destroy (GTK_WIDGET (totem->win)); + + gnome_vfs_shutdown (); + + exit (0); +} + +static void +totem_action_menu_popup (Totem *totem, guint button) +{ + GtkWidget *menu; + + menu = gtk_ui_manager_get_widget (totem->ui_manager, + "/totem-main-popup"); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + button, gtk_get_current_event_time ()); + gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); +} + +static gboolean +main_window_destroy_cb (GtkWidget *widget, GdkEvent *event, Totem *totem) +{ + totem_action_exit (totem); + + return FALSE; +} + +static void +play_pause_set_label (Totem *totem, TotemStates state) +{ + GtkAction *action; + const char *id, *tip; + GSList *l, *proxies; + + if (state == totem->state) + return; + + switch (state) + { + case STATE_PLAYING: + totem_statusbar_set_text (TOTEM_STATUSBAR (totem->statusbar), + _("Playing")); + id = GTK_STOCK_MEDIA_PAUSE; + tip = N_("Pause"); + break; + case STATE_PAUSED: + totem_statusbar_set_text (TOTEM_STATUSBAR (totem->statusbar), + _("Paused")); + id = GTK_STOCK_MEDIA_PLAY; + tip = N_("Play"); + break; + case STATE_STOPPED: + totem_statusbar_set_text (TOTEM_STATUSBAR (totem->statusbar), + _("Stopped")); + totem_statusbar_set_time_and_length + (TOTEM_STATUSBAR (totem->statusbar), 0, 0); + id = GTK_STOCK_MEDIA_PLAY; + tip = N_("Play"); + break; + default: + return; + } + + action = gtk_action_group_get_action (totem->main_action_group, "play"); + g_object_set (G_OBJECT (action), + "tooltip", _(tip), + "stock-id", id, NULL); + + proxies = gtk_action_get_proxies (action); + for (l = proxies; l != NULL; l = l->next) { + atk_object_set_name (gtk_widget_get_accessible (l->data), + _(tip)); + } + + totem->state = state; +} + +void +totem_action_eject (Totem *totem) +{ + GError *err = NULL; + char *cmd, *prefix; + const char *needle; + char *device; + + needle = strchr (totem->mrl, ':'); + g_assert (needle != NULL); + /* we want the ':' as well */ + prefix = g_strndup (totem->mrl, needle - totem->mrl + 1); + totem_playlist_clear_with_prefix (totem->playlist, prefix); + g_free (prefix); + + g_object_get (G_OBJECT (totem->bvw), + "mediadev", &device, NULL); + cmd = g_strdup_printf ("eject %s", device); + g_free (device); + + if (g_spawn_command_line_sync (cmd, NULL, NULL, NULL, &err) == FALSE) + { + totem_action_error (_("Totem could not eject the optical media."), err->message, totem); + g_error_free (err); + } + g_free (cmd); +} + +void +totem_action_show_properties (Totem *totem) +{ + totem_sidebar_set_current_page (totem, "properties"); +} + +void +totem_action_play (Totem *totem) +{ + GError *err = NULL; + int retval; + + if (totem->mrl == NULL) + return; + + if (bacon_video_widget_is_playing (totem->bvw) != FALSE) + return; + + retval = bacon_video_widget_play (totem->bvw, &err); + play_pause_set_label (totem, retval ? STATE_PLAYING : STATE_STOPPED); + if (totem_is_fullscreen (totem) != FALSE) + totem_scrsaver_set_state (totem->scr, !retval); + + if (retval == FALSE) + { + char *msg, *disp; + + disp = totem_uri_escape_for_display (totem->mrl); + msg = g_strdup_printf(_("Totem could not play '%s'."), disp); + g_free (disp); + + totem_playlist_set_playing (totem->playlist, FALSE); + totem_action_error (msg, err->message, totem); + totem_action_stop (totem); + g_free (msg); + g_error_free (err); + } +} + +static void +totem_action_seek (Totem *totem, double pos) +{ + GError *err = NULL; + int retval; + + if (totem->mrl == NULL) + return; + if (bacon_video_widget_is_seekable (totem->bvw) == FALSE) + return; + + retval = bacon_video_widget_seek (totem->bvw, pos, &err); + + if (retval == FALSE) + { + char *msg, *disp; + + disp = totem_uri_escape_for_display (totem->mrl); + msg = g_strdup_printf(_("Totem could not play '%s'."), disp); + g_free (disp); + + totem_playlist_set_playing (totem->playlist, FALSE); + totem_action_error (msg, err->message, totem); + totem_action_stop (totem); + g_free (msg); + g_error_free (err); + } +} + +void +totem_action_set_mrl_and_play (Totem *totem, const char *mrl) +{ + if (totem_action_set_mrl (totem, mrl) != FALSE) + totem_action_play (totem); +} + +static gboolean +totem_action_open_dialog (Totem *totem, const char *path, gboolean play) +{ + GSList *filenames; + gboolean playlist_modified; + + filenames = totem_add_files (GTK_WINDOW (totem->win), path); + + if (filenames == NULL) + return FALSE; + + playlist_modified = totem_action_open_files_list (totem, + filenames); + + if (playlist_modified == FALSE) { + g_slist_foreach (filenames, (GFunc) g_free, NULL); + g_slist_free (filenames); + return FALSE; + } + + g_slist_foreach (filenames, (GFunc) g_free, NULL); + g_slist_free (filenames); + + if (play != FALSE) { + char *mrl; + + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } + + return TRUE; +} + +static gboolean +totem_action_load_media (Totem *totem, MediaType type) +{ + char **mrls; + char *msg; + gboolean retval; + + if (bacon_video_widget_can_play (totem->bvw, type) == FALSE) + { + msg = g_strdup_printf (_("Totem cannot play this type of media (%s) because you do not have the appropriate plugins to handle it."), _(totem_cd_get_human_readable_name (type))); + totem_action_error (msg, _("Please install the necessary plugins and restart Totem to be able to play this media."), totem); + g_free (msg); + return FALSE; + } + + mrls = bacon_video_widget_get_mrls (totem->bvw, type); + if (mrls == NULL) + { + msg = g_strdup_printf (_("Totem could not play this media (%s) although a plugin is present to handle it."), _(totem_cd_get_human_readable_name (type))); + totem_action_error (msg, _("You might want to check that a disc is present in the drive and that it is correctly configured."), totem); + g_free (msg); + return FALSE; + } + + retval = totem_action_open_files (totem, mrls); + g_strfreev (mrls); + + return retval; +} + +static gboolean +totem_action_load_media_device (Totem *totem, const char *device) +{ + MediaType type; + GError *error = NULL; + char *device_path, *url; + gboolean retval; + + if (g_str_has_prefix (device, "file://") != FALSE) + device_path = g_filename_from_uri (device, NULL, NULL); + else + device_path = g_strdup (device); + + type = totem_cd_detect_type_with_url (device_path, &url, &error); + + switch (type) { + case MEDIA_TYPE_ERROR: + totem_action_error (_("Totem was not able to play this disc."), + error ? error->message : _("No reason."), + totem); + retval = FALSE; + break; + case MEDIA_TYPE_DATA: + /* Set default location to the mountpoint of + * this device */ + retval = totem_action_open_dialog (totem, url, FALSE); + break; + case MEDIA_TYPE_DVD: + case MEDIA_TYPE_VCD: + case MEDIA_TYPE_CDDA: + bacon_video_widget_set_media_device + (BACON_VIDEO_WIDGET (totem->bvw), device_path); + retval = totem_action_load_media (totem, type); + if (retval == FALSE) { + totem_action_set_mrl_and_play (totem, NULL); + } + break; + default: + g_assert_not_reached (); + } + + g_free (url); + g_free (device_path); + + return retval; +} + +void +totem_action_play_media_device (Totem *totem, const char *device) +{ + char *mrl; + + if (totem_action_load_media_device (totem, device) != FALSE) { + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } +} + +void +totem_action_play_media (Totem *totem, MediaType type) +{ + char *mrl; + + if (totem_action_load_media (totem, type) != FALSE) { + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } +} + +void +totem_action_stop (Totem *totem) +{ + bacon_video_widget_stop (totem->bvw); + totem_scrsaver_enable (totem->scr); +} + +void +totem_action_play_pause (Totem *totem) +{ + if (totem->mrl == NULL) + { + char *mrl; + + /* Try to pull an mrl from the playlist */ + mrl = totem_playlist_get_current_mrl (totem->playlist); + if (mrl == NULL) + { + play_pause_set_label (totem, STATE_STOPPED); + return; + } else { + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + return; + } + } + + if (bacon_video_widget_is_playing (totem->bvw) == FALSE) + { + bacon_video_widget_play (totem->bvw, NULL); + play_pause_set_label (totem, STATE_PLAYING); + if (totem_is_fullscreen (totem) != FALSE) + totem_scrsaver_disable (totem->scr); + } else { + bacon_video_widget_pause (totem->bvw); + play_pause_set_label (totem, STATE_PAUSED); + totem_scrsaver_enable (totem->scr); + } +} + +void +totem_action_pause (Totem *totem) +{ + if (bacon_video_widget_is_playing (totem->bvw) != FALSE) + { + bacon_video_widget_pause (totem->bvw); + play_pause_set_label (totem, STATE_PAUSED); + totem_scrsaver_enable (totem->scr); + } +} + +static void +totem_action_set_cursor (Totem *totem, gboolean state) +{ + totem->cursor_shown = state; + bacon_video_widget_set_show_cursor (totem->bvw, state); +} + +static gboolean +window_state_event_cb (GtkWidget *window, GdkEventWindowState *event, + Totem *totem) +{ + if (event->changed_mask == GDK_WINDOW_STATE_MAXIMIZED) { + totem->maximised = (event->new_window_state + & GDK_WINDOW_STATE_MAXIMIZED); + return FALSE; + } + + if (event->changed_mask != GDK_WINDOW_STATE_FULLSCREEN) { + return FALSE; + } + + if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) { + totem_action_save_size (totem); + update_fullscreen_size (totem); + bacon_video_widget_set_fullscreen (totem->bvw, TRUE); + totem_action_set_cursor (totem, FALSE); + + if (bacon_video_widget_is_playing (totem->bvw) != FALSE) + totem_scrsaver_disable (totem->scr); + + totem->controls_visibility = TOTEM_CONTROLS_FULLSCREEN; + show_controls (totem, FALSE); + totem_action_set_sensitivity ("fullscreen", FALSE); + } else { + GtkAction *action; + + popup_hide (totem); + bacon_video_widget_set_fullscreen (totem->bvw, FALSE); + totem_action_set_cursor (totem, TRUE); + + totem_scrsaver_enable (totem->scr); + + action = gtk_action_group_get_action (totem->main_action_group, + "show-controls"); + + if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) + totem->controls_visibility = TOTEM_CONTROLS_VISIBLE; + else + totem->controls_visibility = TOTEM_CONTROLS_HIDDEN; + + show_controls (totem, TRUE); + totem_action_set_sensitivity ("fullscreen", TRUE); + } + + return FALSE; +} + +void +totem_action_fullscreen_toggle (Totem *totem) +{ + if (totem_is_fullscreen (totem) != FALSE) { + gtk_window_unfullscreen (GTK_WINDOW (totem->win)); + } else { + gtk_window_fullscreen (GTK_WINDOW (totem->win)); + } +} + +void +totem_action_fullscreen (Totem *totem, gboolean state) +{ + if (totem_is_fullscreen (totem) == state) + return; + + totem_action_fullscreen_toggle (totem); +} + +void +totem_action_open (Totem *totem) +{ + totem_action_open_dialog (totem, NULL, TRUE); +} + +static gint +totem_compare_recent_stream_items (GtkRecentInfo *a, GtkRecentInfo *b) +{ + gboolean has_totem_a, has_totem_b; + + has_totem_a = gtk_recent_info_has_group (a, "TotemStreams"); + has_totem_b = gtk_recent_info_has_group (b, "TotemStreams"); + + if (has_totem_a && has_totem_b) { + time_t time_a, time_b; + + time_a = gtk_recent_info_get_modified (a); + time_b = gtk_recent_info_get_modified (b); + + return (time_b - time_a); + } else if (has_totem_a) { + return -1; + } else if (has_totem_b) { + return 1; + } + + return 0; +} + +static char * +totem_open_location_set_from_clipboard (Totem *totem) +{ + GtkClipboard *clipboard; + gchar *clipboard_content; + + /* Initialize the clipboard and get its content */ + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (totem->win), GDK_SELECTION_CLIPBOARD); + clipboard_content = gtk_clipboard_wait_for_text (clipboard); + + /* Check clipboard for "://". If it exists, return it */ + if (clipboard_content != NULL && strcmp (clipboard_content, "") != 0) + { + if (g_strrstr (clipboard_content, "://") != NULL) + return clipboard_content; + } + + g_free (clipboard_content); + return NULL; +} + +static gboolean +totem_open_location_match (GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data) +{ + /* Substring-match key against uri */ + char *uri, *match; + + g_return_val_if_fail (key != NULL, FALSE); + gtk_tree_model_get (user_data, iter, 0, &uri, -1); + g_return_val_if_fail (uri != NULL, FALSE); + match = strstr (uri, key); + g_free (uri); + + return (match != NULL); +} + +void +totem_action_open_location (Totem *totem) +{ + GladeXML *glade; + char *mrl, *clipboard_location; + GtkWidget *dialog, *entry; + int response; + const char *filenames[2]; + GtkEntryCompletion *completion; + GtkTreeModel *model; + GList *recent_items; + + glade = totem_interface_load ("uri.glade", _("Open Location..."), + FALSE, GTK_WINDOW (totem->win)); + if (glade == NULL) + return; + + /* Get item from clipboard to fill entry in Open Location... dialog */ + clipboard_location = totem_open_location_set_from_clipboard (totem); + if (clipboard_location != NULL && strcmp (clipboard_location, "") != 0) + gtk_entry_set_text (GTK_ENTRY (glade_xml_get_widget (glade, "uri")), clipboard_location); + g_free (clipboard_location); + + dialog = glade_xml_get_widget (glade, "open_uri_dialog"); + entry = glade_xml_get_widget (glade, "uri"); + + completion = gtk_entry_completion_new(); + model = GTK_TREE_MODEL (gtk_list_store_new (1, G_TYPE_STRING)); + gtk_entry_set_completion (GTK_ENTRY (entry), completion); + + /* Add items in Totem's GtkRecentManager to the uri_list GtkEntry's GtkEntryCompletion */ + recent_items = gtk_recent_manager_get_items (totem->recent_manager); + + if (recent_items != NULL) + { + GList *p; + GtkTreeIter iter; + + recent_items = g_list_sort (recent_items, (GCompareFunc) totem_compare_recent_stream_items); + + for (p = recent_items; p != NULL; p = p->next) + { + if (!gtk_recent_info_has_group ((GtkRecentInfo *) p->data, "TotemStreams")) + continue; + + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, gtk_recent_info_get_uri ((GtkRecentInfo *) p->data), -1); + gtk_recent_info_unref ((GtkRecentInfo *) p->data); + } + } + + g_list_free (recent_items); + + gtk_entry_completion_set_model (completion, model); + gtk_entry_completion_set_text_column (completion, 0); + gtk_entry_completion_set_match_func (completion, (GtkEntryCompletionMatchFunc) totem_open_location_match, model, NULL); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response == GTK_RESPONSE_OK) + { + char *uri; + + uri = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + + if (uri != NULL && strcmp (uri, "") != 0) + { + if (g_strrstr (uri, "://") == NULL) + { + char *tmp; + tmp = g_strconcat ("http://", uri, NULL); + g_free (uri); + uri = tmp; + } + + filenames[0] = uri; + filenames[1] = NULL; + totem_action_open_files (totem, (char **) filenames); + + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } + g_free (uri); + } + + gtk_widget_destroy (dialog); + g_object_unref (glade); +} + +void +totem_action_take_screenshot (Totem *totem) +{ + GdkPixbuf *pixbuf; + GtkWidget *dialog; + char *filename; + GError *err = NULL; + + if (bacon_video_widget_get_logo_mode (totem->bvw) != FALSE) + return; + + if (bacon_video_widget_can_get_frames (totem->bvw, &err) == FALSE) + { + if (err == NULL) + return; + + totem_action_error (_("Totem could not get a screenshot of that film."), err->message, totem); + g_error_free (err); + return; + } + + pixbuf = bacon_video_widget_get_current_frame (totem->bvw); + if (pixbuf == NULL) + { + totem_action_error (_("Totem could not get a screenshot of that film."), _("This is not supposed to happen; please file a bug report."), totem); + return; + } + + filename = g_build_filename (DATADIR, + "totem", "screenshot.glade", NULL); + dialog = totem_screenshot_new (filename, pixbuf); + g_free (filename); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + g_object_unref (pixbuf); +} + +static char * +totem_get_nice_name_for_stream (Totem *totem) +{ + char *title, *artist, *retval; + int tracknum; + GValue value = { 0, }; + + bacon_video_widget_get_metadata (totem->bvw, BVW_INFO_TITLE, &value); + title = g_value_dup_string (&value); + g_value_unset (&value); + + if (title == NULL) + return NULL; + + bacon_video_widget_get_metadata (totem->bvw, BVW_INFO_ARTIST, &value); + artist = g_value_dup_string (&value); + g_value_unset (&value); + + if (artist == NULL) + return title; + + bacon_video_widget_get_metadata (totem->bvw, BVW_INFO_TRACK_NUMBER, + &value); + tracknum = g_value_get_int (&value); + + if (tracknum != 0) { + retval = g_strdup_printf ("%02d. %s - %s", + tracknum, artist, title); + } else { + retval = g_strdup_printf ("%s - %s", artist, title); + } + g_free (artist); + g_free (title); + + return retval; +} + +static void +update_skip_to (Totem *totem, gint64 time) +{ + if (totem->skipto != NULL) + totem_skipto_update_range (totem->skipto, time); +} + +static void +update_mrl_label (Totem *totem, const char *name) +{ + gint time; + char *text; + GtkWidget *widget; + + if (name != NULL) + { + char *escaped; + + /* Get the length of the stream */ + time = bacon_video_widget_get_stream_length (totem->bvw); + totem_statusbar_set_time_and_length (TOTEM_STATUSBAR + (totem->statusbar), 0, time / 1000); + + update_skip_to (totem, time); + + /* Update the mrl label */ + escaped = g_markup_escape_text (name, strlen (name)); + text = g_strdup_printf + ("<span size=\"medium\"><b>%s</b></span>", escaped); + g_free (escaped); + + widget = glade_xml_get_widget (totem->xml, "tcw_title_label"); + gtk_label_set_markup (GTK_LABEL (widget), text); + + g_free (text); + + /* Title */ + gtk_window_set_title (GTK_WINDOW (totem->win), name); + } else { + totem_statusbar_set_time_and_length (TOTEM_STATUSBAR + (totem->statusbar), 0, 0); + totem_statusbar_set_text (TOTEM_STATUSBAR (totem->statusbar), + _("Stopped")); + + update_skip_to (totem, 0); + + /* Update the mrl label */ + text = g_strdup_printf + ("<span size=\"medium\"><b>%s</b></span>", + _("No File")); + widget = glade_xml_get_widget (totem->xml, "tcw_title_label"); + gtk_label_set_markup (GTK_LABEL (widget), text); + + g_free (text); + + /* Title */ + gtk_window_set_title (GTK_WINDOW (totem->win), _("Totem Movie Player")); + } +} + +gboolean +totem_action_set_mrl_with_warning (Totem *totem, const char *mrl, + gboolean warn) +{ + gboolean retval = TRUE; + + if (totem->mrl != NULL) + { + g_free (totem->mrl); + totem->mrl = NULL; + bacon_video_widget_close (totem->bvw); + } + + /* Reset the properties and wait for the signal*/ + bacon_video_widget_properties_reset + (BACON_VIDEO_WIDGET_PROPERTIES (totem->properties)); + + if (mrl == NULL) + { + retval = FALSE; + + /* Play/Pause */ + totem_action_set_sensitivity ("play", FALSE); + + /* Seek bar and seek buttons */ + update_seekable (totem, FALSE); + + /* Volume */ + totem_main_set_sensitivity ("tcw_volume_button", FALSE); + totem_action_set_sensitivity ("volume-up", FALSE); + totem_action_set_sensitivity ("volume-down", FALSE); + totem->volume_sensitive = FALSE; + + /* Control popup */ + gtk_widget_set_sensitive (totem->fs_seek, FALSE); + totem_action_set_sensitivity ("next-chapter", FALSE); + totem_action_set_sensitivity ("previous-chapter", FALSE); + totem_main_set_sensitivity ("tcw_volume_hbox", FALSE); + + /* Take a screenshot */ + totem_action_set_sensitivity ("take-screenshot", FALSE); + + /* Clear the playlist */ + totem_action_set_sensitivity ("clear-playlist", FALSE); + + /* Set the logo */ + bacon_video_widget_set_logo_mode (totem->bvw, TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (totem->properties), + FALSE); + update_mrl_label (totem, NULL); + } else { + gboolean caps; + char *subtitle_uri; + GError *err = NULL; + + bacon_video_widget_set_logo_mode (totem->bvw, FALSE); + + subtitle_uri = totem_uri_get_subtitle_uri (mrl); + totem_gdk_window_set_waiting_cursor (totem->win->window); + retval = bacon_video_widget_open_with_subtitle (totem->bvw, + mrl, subtitle_uri, &err); + gdk_window_set_cursor (totem->win->window, NULL); + totem->mrl = g_strdup (mrl); + + /* Play/Pause */ + totem_action_set_sensitivity ("play", TRUE); + + /* Seek bar */ + update_seekable (totem, + bacon_video_widget_is_seekable (totem->bvw)); + + /* Volume */ + caps = bacon_video_widget_can_set_volume (totem->bvw); + totem_main_set_sensitivity ("tcw_volume_button", caps); + totem_main_set_sensitivity ("tcw_volume_hbox", caps); + totem_action_set_sensitivity ("volume-up", caps && totem->prev_volume < 100); + totem_action_set_sensitivity ("volume-down", caps && totem->prev_volume > 0); + totem->volume_sensitive = caps; + + /* Take a screenshot */ + totem_action_set_sensitivity ("take-screenshot", retval); + + /* Clear the playlist */ + totem_action_set_sensitivity ("clear-playlist", retval); + + gtk_widget_set_sensitive + (GTK_WIDGET (totem->properties), retval); + + /* Set the playlist */ + totem_playlist_set_playing (totem->playlist, retval); + + if (retval == FALSE && warn != FALSE) + { + char *msg, *disp; + + disp = totem_uri_escape_for_display (totem->mrl); + msg = g_strdup_printf(_("Totem could not play '%s'."), disp); + g_free (disp); + if (err && err->message) { + totem_action_error (msg, err->message, totem); + } + else { + totem_action_error (msg, _("No error message"), totem); + } + g_free (msg); + } + + if (retval == FALSE) + { + if (err) { + g_error_free (err); + } + g_free (totem->mrl); + totem->mrl = NULL; + play_pause_set_label (totem, STATE_STOPPED); + bacon_video_widget_set_logo_mode (totem->bvw, TRUE); + } + } + update_buttons (totem); + update_media_menu_items (totem); + + return retval; +} + +gboolean +totem_action_set_mrl (Totem *totem, const char *mrl) +{ + return totem_action_set_mrl_with_warning (totem, mrl, TRUE); +} + +static gboolean +totem_time_within_seconds (Totem *totem) +{ + gint64 time; + + time = bacon_video_widget_get_current_time (totem->bvw); + + return (time < REWIND_OR_PREVIOUS); +} + +static void +totem_action_direction (Totem *totem, TotemPlaylistDirection dir) +{ + if (totem_playing_dvd (totem->mrl) == FALSE && + totem_playlist_has_direction (totem->playlist, dir) == FALSE + && totem_playlist_get_repeat (totem->playlist) == FALSE) + return; + + if (totem_playing_dvd (totem->mrl) != FALSE) + { + bacon_video_widget_dvd_event (totem->bvw, + dir == TOTEM_PLAYLIST_DIRECTION_NEXT ? + BVW_DVD_NEXT_CHAPTER : + BVW_DVD_PREV_CHAPTER); + return; + } + + if (dir == TOTEM_PLAYLIST_DIRECTION_NEXT + || bacon_video_widget_is_seekable (totem->bvw) == FALSE + || totem_time_within_seconds (totem) != FALSE) + { + char *mrl; + + totem_playlist_set_direction (totem->playlist, dir); + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } else { + totem_action_seek (totem, 0); + } +} + +void +totem_action_previous (Totem *totem) +{ + totem_action_direction (totem, TOTEM_PLAYLIST_DIRECTION_PREVIOUS); +} + +void +totem_action_next (Totem *totem) +{ + totem_action_direction (totem, TOTEM_PLAYLIST_DIRECTION_NEXT); +} + +void +totem_action_seek_relative (Totem *totem, int off_sec) +{ + GError *err = NULL; + gint64 off_msec, oldsec, sec; + + if (totem->mrl == NULL) + return; + if (bacon_video_widget_is_seekable (totem->bvw) == FALSE) + return; + + off_msec = off_sec * 1000; + oldsec = bacon_video_widget_get_current_time (totem->bvw); + sec = MAX (0, oldsec + off_msec); + + bacon_video_widget_seek_time (totem->bvw, sec, &err); + + if (err != NULL) + { + char *msg, *disp; + + disp = totem_uri_escape_for_display (totem->mrl); + msg = g_strdup_printf(_("Totem could not play '%s'."), totem->mrl); + g_free (disp); + + totem_playlist_set_playing (totem->playlist, FALSE); + totem_action_stop (totem); + totem_action_error (msg, err->message, totem); + g_free (msg); + g_error_free (err); + } +} + +static void +totem_action_zoom (Totem *totem, int zoom) +{ + GtkAction *action; + gboolean zoom_reset, zoom_in, zoom_out; + + if (zoom == ZOOM_ENABLE) + zoom = bacon_video_widget_get_zoom (totem->bvw); + + if (zoom == ZOOM_DISABLE) { + zoom_reset = zoom_in = zoom_out = FALSE; + } else if (zoom < ZOOM_LOWER || zoom > ZOOM_UPPER) { + return; + } else { + bacon_video_widget_set_zoom (totem->bvw, zoom); + zoom_reset = (zoom != ZOOM_RESET); + zoom_out = zoom != ZOOM_LOWER; + zoom_in = zoom != ZOOM_UPPER; + } + + action = gtk_action_group_get_action (totem->zoom_action_group, + "zoom-in"); + gtk_action_set_sensitive (action, zoom_in); + + action = gtk_action_group_get_action (totem->zoom_action_group, + "zoom-out"); + gtk_action_set_sensitive (action, zoom_out); + + action = gtk_action_group_get_action (totem->zoom_action_group, + "zoom-reset"); + gtk_action_set_sensitive (action, zoom_reset); +} + +void +totem_action_zoom_relative (Totem *totem, int off_pct) +{ + int zoom; + + zoom = bacon_video_widget_get_zoom (totem->bvw); + totem_action_zoom (totem, zoom + off_pct); +} + +void +totem_action_zoom_reset (Totem *totem) +{ + totem_action_zoom (totem, 100); +} + +void +totem_action_volume_relative (Totem *totem, int off_pct) +{ + int vol; + + if (bacon_video_widget_can_set_volume (totem->bvw) == FALSE) + return; + + vol = bacon_video_widget_get_volume (totem->bvw); + bacon_video_widget_set_volume (totem->bvw, vol + off_pct); +} + +void +totem_action_toggle_aspect_ratio (Totem *totem) +{ + GtkAction *action; + int tmp; + + tmp = totem_action_get_aspect_ratio (totem); + tmp++; + if (tmp > 4) + tmp = 0; + + action = gtk_action_group_get_action (totem->main_action_group, "aspect-ratio-auto"); + gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), tmp); +} + +void +totem_action_set_aspect_ratio (Totem *totem, int ratio) +{ + bacon_video_widget_set_aspect_ratio (totem->bvw, ratio); +} + +int +totem_action_get_aspect_ratio (Totem *totem) +{ + return (bacon_video_widget_get_aspect_ratio (totem->bvw)); +} + +void +totem_action_set_scale_ratio (Totem *totem, gfloat ratio) +{ + bacon_video_widget_set_scale_ratio (totem->bvw, ratio); +} + +void +totem_action_show_help (Totem *totem) +{ +#ifndef HAVE_GTK_ONLY + GError *err = NULL; + + if (gnome_help_display ("totem.xml", NULL, &err) == FALSE) + { + totem_action_error (_("Totem could not display the help contents."), err->message, totem); + g_error_free (err); + } +#endif /* !HAVE_GTK_ONLY */ +} + +static gboolean +totem_action_drop_files (Totem *totem, GtkSelectionData *data, + int drop_type, gboolean empty_pl) +{ + GList *list, *p, *file_list; + gboolean cleared = FALSE; + + list = gnome_vfs_uri_list_parse ((const char *)data->data); + + if (list == NULL) + return FALSE; + + p = list; + file_list = NULL; + + while (p != NULL) + { + file_list = g_list_prepend (file_list, + gnome_vfs_uri_to_string + ((const GnomeVFSURI*)(p->data), 0)); + p = p->next; + } + + gnome_vfs_uri_list_free (list); + if (file_list == NULL) + return FALSE; + + if (drop_type != 1) + file_list = g_list_sort (file_list, (GCompareFunc) strcmp); + else + file_list = g_list_reverse (file_list); + + for (p = file_list; p != NULL; p = p->next) + { + char *filename, *title; + + if (p->data == NULL) + continue; + + filename = totem_create_full_path (p->data); + title = NULL; + + if (empty_pl != FALSE && cleared == FALSE) + { + /* The function that calls us knows better + * if we should be doing something with the + * changed playlist ... */ + g_signal_handlers_disconnect_by_func + (G_OBJECT (totem->playlist), + playlist_changed_cb, totem); + totem_playlist_clear (totem->playlist); + cleared = TRUE; + } + + /* Super _NETSCAPE_URL trick */ + if (drop_type == 1) + { + g_free (p->data); + p = p->next; + if (p != NULL) { + if (g_str_has_prefix (p->data, "file:") != FALSE) + title = (char *)p->data + 5; + else + title = p->data; + } + } + + totem_playlist_add_mrl (totem->playlist, filename, title); + + g_free (filename); + g_free (p->data); + } + + g_list_free (file_list); + + /* ... and reconnect because we're nice people */ + if (cleared != FALSE) + { + char *mrl; + + g_signal_connect (G_OBJECT (totem->playlist), + "changed", G_CALLBACK (playlist_changed_cb), + totem); + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); + } + + return TRUE; +} + +static void +drop_video_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + Totem *totem) +{ + gboolean retval; + + retval = totem_action_drop_files (totem, data, info, TRUE); + gtk_drag_finish (context, retval, FALSE, time); +} + +static void +drop_playlist_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + Totem *totem) +{ + gboolean retval; + + retval = totem_action_drop_files (totem, data, info, FALSE); + gtk_drag_finish (context, retval, FALSE, time); +} + +static void +drag_video_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + gpointer callback_data) +{ + Totem *totem = (Totem *) callback_data; + char *text; + int len; + + g_assert (selection_data != NULL); + + if (totem->mrl == NULL) + return; + + if (totem->mrl[0] == '/') + text = gnome_vfs_get_uri_from_local_path (totem->mrl); + else + text = g_strdup (totem->mrl); + + g_return_if_fail (text != NULL); + + len = strlen (text); + + gtk_selection_data_set (selection_data, + selection_data->target, + 8, (guchar *) text, len); + + g_free (text); +} + +static void +on_got_redirect (BaconVideoWidget *bvw, const char *mrl, Totem *totem) +{ + gchar *old_mrl, *new_mrl; + + old_mrl = totem_playlist_get_current_mrl (TOTEM_PLAYLIST (totem->playlist)); + new_mrl = totem_resolve_relative_link (old_mrl, mrl); + g_free (old_mrl); + + bacon_video_widget_close (totem->bvw); + totem_gdk_window_set_waiting_cursor (totem->win->window); + bacon_video_widget_open (totem->bvw, new_mrl, NULL); + gdk_window_set_cursor (totem->win->window, NULL); + bacon_video_widget_play (bvw, NULL); + g_free (new_mrl); +} + +/* This is only called when we are playing a DVD */ +static void +on_title_change_event (BaconVideoWidget *bvw, const char *string, Totem *totem) +{ + update_mrl_label (totem, string); + update_buttons (totem); + totem_playlist_set_title (TOTEM_PLAYLIST (totem->playlist), + string, TRUE); +} + +static void +on_channels_change_event (BaconVideoWidget *bvw, Totem *totem) +{ + gchar *name; + + totem_sublang_update (totem); + + /* updated stream info (new song) */ + name = totem_get_nice_name_for_stream (totem); + + bacon_video_widget_properties_update + (BACON_VIDEO_WIDGET_PROPERTIES (totem->properties), + totem->bvw); + + if (name != NULL) { + update_mrl_label (totem, name); + totem_playlist_set_title + (TOTEM_PLAYLIST (totem->playlist), name, TRUE); + g_free (name); + } +} + +static void +on_playlist_change_name (TotemPlaylist *playlist, Totem *totem) +{ + char *name, *artist, *album, *title; + gboolean cur; + + if ((name = totem_playlist_get_current_title (playlist, + &cur)) != NULL) { + update_mrl_label (totem, name); + g_free (name); + } + + if (totem_playlist_get_current_metadata (playlist, &artist, + &title, &album) != FALSE) { + bacon_video_widget_properties_from_metadata ( + BACON_VIDEO_WIDGET_PROPERTIES (totem->properties), + artist, title, album); + + g_free (artist); + g_free (album); + g_free (title); + } +} + +static void +on_got_metadata_event (BaconVideoWidget *bvw, Totem *totem) +{ + char *name = NULL; + + bacon_video_widget_properties_update + (BACON_VIDEO_WIDGET_PROPERTIES (totem->properties), + totem->bvw); + + name = totem_get_nice_name_for_stream (totem); + + if (name != NULL) { + totem_playlist_set_title + (TOTEM_PLAYLIST (totem->playlist), name, FALSE); + g_free (name); + } + on_playlist_change_name + (TOTEM_PLAYLIST (totem->playlist), totem); +} + +static void +on_error_event (BaconVideoWidget *bvw, char *message, + gboolean playback_stopped, gboolean fatal, Totem *totem) +{ + if (playback_stopped) + play_pause_set_label (totem, STATE_STOPPED); + + if (fatal == FALSE) { + totem_action_error (_("An error occurred"), message, totem); + } else { + totem_action_error_and_exit (_("An error occurred"), + message, totem); + } +} + +static void +on_buffering_event (BaconVideoWidget *bvw, int percentage, Totem *totem) +{ + totem_statusbar_push (TOTEM_STATUSBAR (totem->statusbar), percentage); +} + +static void +update_seekable (Totem *totem, gboolean seekable) +{ + if (totem->seekable == seekable) + return; + + totem->seekable = seekable; + + /* Check if the stream is seekable */ + gtk_widget_set_sensitive (totem->seek, seekable); + gtk_widget_set_sensitive (totem->fs_seek, seekable); + + totem_main_set_sensitivity ("tmw_seek_hbox", seekable); + + totem_main_set_sensitivity ("tcw_time_hbox", seekable); + + totem_action_set_sensitivity ("skip-forward", seekable); + totem_action_set_sensitivity ("skip-backwards", seekable); + totem_action_set_sensitivity ("skip-to", seekable); + if (totem->skipto) + totem_skipto_set_seekable (totem->skipto, seekable); +} + +static void +update_current_time (BaconVideoWidget *bvw, + gint64 current_time, + gint64 stream_length, + float current_position, + gboolean seekable, Totem *totem) +{ + update_skip_to (totem, stream_length); + update_seekable (totem, seekable); + + if (totem->seek_lock == FALSE) + { + gtk_adjustment_set_value (totem->seekadj, + current_position * 65535); + gtk_adjustment_set_value (totem->fs_seekadj, + current_position * 65535); + + if (stream_length == 0 && totem->mrl != NULL) + { + totem_statusbar_set_time_and_length + (TOTEM_STATUSBAR (totem->statusbar), + (int) (current_time / 1000), -1); + } else { + totem_statusbar_set_time_and_length + (TOTEM_STATUSBAR (totem->statusbar), + (int) (current_time / 1000), + (int) (stream_length / 1000)); + } + + totem_time_label_set_time + (TOTEM_TIME_LABEL (totem->tcw_time_label), + current_time, stream_length); + bacon_video_widget_properties_from_time + (BACON_VIDEO_WIDGET_PROPERTIES (totem->properties), + stream_length); + } +} + +static gboolean +vol_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, Totem *totem) +{ + totem->vol_fs_lock = TRUE; + return FALSE; +} + +static gboolean +vol_slider_released_cb (GtkWidget *widget, GdkEventButton *event, Totem *totem) +{ + totem->vol_fs_lock = FALSE; + return FALSE; +} + +static void +update_volume_sliders (Totem *totem) +{ + int volume; + GtkAction *action; + + volume = bacon_video_widget_get_volume (totem->bvw); + + if (totem->volume_first_time || (totem->prev_volume != volume && + totem->prev_volume != -1 && volume != -1)) + { + totem->volume_first_time = 0; + bacon_volume_button_set_value ( + BACON_VOLUME_BUTTON (totem->volume), (float) volume); + gtk_adjustment_set_value (totem->fs_voladj, + (float) volume); + + action = gtk_action_group_get_action (totem->main_action_group, "volume-down"); + gtk_action_set_sensitive (action, volume > 0 && totem->volume_sensitive); + + action = gtk_action_group_get_action (totem->main_action_group, "volume-up"); + gtk_action_set_sensitive (action, volume < 100 && totem->volume_sensitive); + } + + totem->prev_volume = volume; +} + +static void +property_notify_cb (BaconVideoWidget *bvw, GParamSpec *spec, Totem *totem) +{ + if (strcmp ("volume", spec->name) == 0) { + update_volume_sliders (totem); + } else if (strcmp ("logo-mode", spec->name) == 0) { + gboolean enabled; + enabled = bacon_video_widget_get_logo_mode (totem->bvw); + totem_action_zoom (totem, enabled ? ZOOM_DISABLE : ZOOM_ENABLE); + } +} + +static gboolean +seek_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, Totem *totem) +{ + totem->seek_lock = TRUE; + totem_statusbar_set_seeking (TOTEM_STATUSBAR (totem->statusbar), TRUE); + totem_time_label_set_seeking (TOTEM_TIME_LABEL (totem->tcw_time_label), TRUE); + + return FALSE; +} + +static void +seek_slider_changed_cb (GtkAdjustment *adj, Totem *totem) +{ + double pos; + gint time; + + if (totem->seek_lock == FALSE) + return; + + pos = gtk_adjustment_get_value (adj) / 65535; + time = bacon_video_widget_get_stream_length (totem->bvw); + totem_statusbar_set_time_and_length (TOTEM_STATUSBAR (totem->statusbar), + (int) (pos * time / 1000), time / 1000); + totem_time_label_set_time + (TOTEM_TIME_LABEL (totem->tcw_time_label), + (int) (pos * time), time); + + if (bacon_video_widget_can_direct_seek (totem->bvw) != FALSE) + totem_action_seek (totem, pos); +} + +static gboolean +seek_slider_released_cb (GtkWidget *widget, GdkEventButton *event, Totem *totem) +{ + GtkAdjustment *adj, *other_adj; + gdouble val; + + adj = gtk_range_get_adjustment (GTK_RANGE (widget)); + other_adj = (adj == totem->seekadj) ? totem->fs_seekadj : totem->seekadj; + + /* set to FALSE here to avoid triggering a final seek when + * syncing the adjustments while being in direct seek mode */ + totem->seek_lock = FALSE; + + /* sync both adjustments */ + val = gtk_adjustment_get_value (adj); + gtk_adjustment_set_value (other_adj, val); + + if (bacon_video_widget_can_direct_seek (totem->bvw) == FALSE) + { + totem_action_seek (totem, val / 65535.0); + } + + totem_statusbar_set_seeking (TOTEM_STATUSBAR (totem->statusbar), FALSE); + totem_time_label_set_seeking (TOTEM_TIME_LABEL (totem->tcw_time_label), + FALSE); + return FALSE; +} + +static void +vol_cb (GtkWidget *widget, Totem *totem) +{ + if (totem->vol_lock == FALSE) + { + totem->vol_lock = TRUE; + + if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "fs")) != FALSE) + { + bacon_video_widget_set_volume + (totem->bvw, (gint) totem->fs_voladj->value); + + /* Update the fullscreen volume adjustment */ + bacon_volume_button_set_value ( + BACON_VOLUME_BUTTON (totem->volume), + gtk_adjustment_get_value (totem->fs_voladj)); + } else { + int value = bacon_volume_button_get_value ( + BACON_VOLUME_BUTTON (totem->volume)); + + bacon_video_widget_set_volume (totem->bvw, value); + /* Update the volume adjustment */ + gtk_adjustment_set_value (totem->fs_voladj, value); + } + + totem->vol_lock = FALSE; + } +} + +static gboolean +totem_action_open_files (Totem *totem, char **list) +{ + GSList *slist = NULL; + int i, retval; + + for (i = 0 ; list[i] != NULL; i++) + slist = g_slist_prepend (slist, list[i]); + + slist = g_slist_reverse (slist); + retval = totem_action_open_files_list (totem, slist); + g_slist_free (slist); + + return retval; +} + +static gboolean +totem_action_open_files_list (Totem *totem, GSList *list) +{ + GSList *l; + gboolean changed; + gboolean cleared; + + changed = FALSE; + cleared = FALSE; + + if (list == NULL) + return changed; + + for (l = list ; l != NULL; l = l->next) + { + char *filename; + char *data = l->data; + + if (data == NULL) + continue; + + /* Ignore relatives paths that start with "--", tough luck */ + if (data[0] == '-' && data[1] == '-') + continue; + + /* Get the subtitle part out for our tests */ + filename = totem_create_full_path (data); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) + || strstr (filename, "#") != NULL + || strstr (filename, "://") != NULL + || g_str_has_prefix (filename, "dvd:") != FALSE + || g_str_has_prefix (filename, "vcd:") != FALSE + || g_str_has_prefix (filename, "cdda:") != FALSE + || g_str_has_prefix (filename, "cd:") != FALSE) + { + if (cleared == FALSE) + { + /* The function that calls us knows better + * if we should be doing something with the + * changed playlist ... */ + g_signal_handlers_disconnect_by_func + (G_OBJECT (totem->playlist), + playlist_changed_cb, totem); + changed = totem_playlist_clear (totem->playlist); + bacon_video_widget_close (totem->bvw); + cleared = TRUE; + } + + if (totem_is_block_device (filename) != FALSE) { + totem_action_load_media_device (totem, data); + changed = TRUE; + } else if (g_str_has_prefix (filename, "cdda:/") != FALSE) { + totem_playlist_add_mrl (totem->playlist, data, NULL); + changed = TRUE; + } else if (totem_playlist_add_mrl (totem->playlist, + filename, NULL) != FALSE) { + totem_action_add_recent (totem, filename); + changed = TRUE; + } + } + + g_free (filename); + } + + /* ... and reconnect because we're nice people */ + if (cleared != FALSE) + { + g_signal_connect (G_OBJECT (totem->playlist), + "changed", G_CALLBACK (playlist_changed_cb), + totem); + } + + return changed; +} + +static void +commit_hide_skip_to (GtkDialog *dialog, gint response, Totem *totem) +{ + GError *err = NULL; + + if (response != GTK_RESPONSE_OK) + { + gtk_widget_destroy (GTK_WIDGET (totem->skipto)); + return; + } + + gtk_widget_hide (GTK_WIDGET (dialog)); + + bacon_video_widget_seek_time (totem->bvw, + totem_skipto_get_range (totem->skipto), &err); + + gtk_widget_destroy (GTK_WIDGET (totem->skipto)); + + if (err != NULL) + { + char *msg, *disp; + + disp = totem_uri_escape_for_display (totem->mrl); + msg = g_strdup_printf(_("Totem could not seek in '%s'."), disp); + g_free (disp); + totem_action_stop (totem); + totem_playlist_set_playing (totem->playlist, FALSE); + totem_action_error (msg, err->message, totem); + g_free (msg); + g_error_free (err); + } +} + +void +totem_action_skip_to (Totem *totem) +{ + char *filename; + TotemSkipto **skipto; + + if (totem->seekable == FALSE) + return; + + if (totem->skipto != NULL) + { + gtk_window_present (GTK_WINDOW (totem->skipto)); + return; + } + + filename = totem_interface_get_full_path ("skip_to.glade"); + totem->skipto = TOTEM_SKIPTO (totem_skipto_new (filename)); + g_free (filename); + + g_signal_connect (G_OBJECT (totem->skipto), "delete-event", + G_CALLBACK (gtk_widget_destroy), NULL); + g_signal_connect (G_OBJECT (totem->skipto), "response", + G_CALLBACK (commit_hide_skip_to), totem); + + skipto = &totem->skipto; + g_object_add_weak_pointer (G_OBJECT (totem->skipto), + (gpointer *) skipto); + + gtk_window_set_transient_for (GTK_WINDOW (totem->skipto), + GTK_WINDOW (totem->win)); + gtk_widget_show (GTK_WIDGET (totem->skipto)); +} + +static void +on_fs_exit1_activate (GtkButton *button, Totem *totem) +{ + totem_action_fullscreen_toggle (totem); +} + +void +show_controls (Totem *totem, gboolean was_fullscreen) +{ + GtkAction *action; + GtkWidget *menubar, *controlbar, *statusbar, *bvw_vbox, *widget; + int width = 0, height = 0; + + if (totem->bvw == NULL) + return; + + menubar = glade_xml_get_widget (totem->xml, "tmw_menubar_box"); + controlbar = glade_xml_get_widget (totem->xml, "tmw_controls_vbox"); + statusbar = glade_xml_get_widget (totem->xml, "tmw_statusbar"); + bvw_vbox = glade_xml_get_widget (totem->xml, "tmw_bvw_vbox"); + widget = GTK_WIDGET (totem->bvw); + + action = gtk_action_group_get_action (totem->main_action_group, "show-controls"); + gtk_action_set_sensitive (action, !totem_is_fullscreen (totem)); + + if (totem->controls_visibility == TOTEM_CONTROLS_VISIBLE) + { + if (was_fullscreen == FALSE) + { + height = widget->allocation.height; + width = widget->allocation.width; + } + + gtk_widget_set_sensitive (menubar, TRUE); + gtk_widget_show (menubar); + gtk_widget_show (controlbar); + gtk_widget_show (statusbar); + if (totem_sidebar_is_visible (totem) != FALSE) { + /* This is uglier then you might expect because of the + resize handle between the video and sidebar. There + is no convenience method to get the handle's width. + */ + GValue value = { 0, }; + GtkWidget *pane; + int handle_size; + + g_value_init (&value, G_TYPE_INT); + pane = glade_xml_get_widget (totem->xml, + "tmw_main_pane"); + gtk_widget_style_get_property (pane, "handle-size", + &value); + handle_size = g_value_get_int (&value); + + gtk_widget_show (totem->sidebar); + width += totem->sidebar->allocation.width + + handle_size; + } else { + gtk_widget_hide (totem->sidebar); + } + + gtk_container_set_border_width (GTK_CONTAINER (bvw_vbox), + BVW_VBOX_BORDER_WIDTH); + + if (was_fullscreen == FALSE) + { + height += menubar->allocation.height + + controlbar->allocation.height + + statusbar->allocation.height + + 2 * BVW_VBOX_BORDER_WIDTH; + width += 2 * BVW_VBOX_BORDER_WIDTH; + gtk_window_resize (GTK_WINDOW(totem->win), + width, height); + } + } else { + if (totem->controls_visibility == TOTEM_CONTROLS_HIDDEN) + { + width = widget->allocation.width; + height = widget->allocation.height; + } + + /* Hide and make the menubar unsensitive */ + gtk_widget_set_sensitive (menubar, FALSE); + gtk_widget_hide (menubar); + + gtk_widget_hide (controlbar); + gtk_widget_hide (statusbar); + gtk_widget_hide (totem->sidebar); + + /* We won't show controls in fullscreen */ + gtk_container_set_border_width (GTK_CONTAINER (bvw_vbox), 0); + + if (totem->controls_visibility == TOTEM_CONTROLS_HIDDEN) + { + gtk_window_resize (GTK_WINDOW(totem->win), + width, height); + } + } +} + +void +totem_action_toggle_controls (Totem *totem) +{ + GtkAction *action; + gboolean state; + + if (totem_is_fullscreen (totem) != FALSE) + return; + + action = gtk_action_group_get_action (totem->main_action_group, + "show-controls"); + state = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), !state); +} + +static void +on_volume_mute_button (GtkButton *button, Totem *totem) +{ + totem_action_volume_relative (totem, -100); +} + +static void +on_volume_max_button (GtkButton *button, Totem *totem) +{ + totem_action_volume_relative (totem, 100); +} + +static void +totem_action_remote (Totem *totem, TotemRemoteCommand cmd, const char *url) +{ + gboolean handled = TRUE; + + switch (cmd) { + case TOTEM_REMOTE_COMMAND_PLAY: + totem_action_play (totem); + break; + case TOTEM_REMOTE_COMMAND_PLAYPAUSE: + totem_action_play_pause (totem); + break; + case TOTEM_REMOTE_COMMAND_PAUSE: + totem_action_pause (totem); + break; + case TOTEM_REMOTE_COMMAND_SEEK_FORWARD: + totem_action_seek_relative (totem, SEEK_FORWARD_OFFSET); + break; + case TOTEM_REMOTE_COMMAND_SEEK_BACKWARD: + totem_action_seek_relative (totem, + SEEK_BACKWARD_OFFSET); + break; + case TOTEM_REMOTE_COMMAND_VOLUME_UP: + totem_action_volume_relative (totem, VOLUME_UP_OFFSET); + break; + case TOTEM_REMOTE_COMMAND_VOLUME_DOWN: + totem_action_volume_relative (totem, VOLUME_DOWN_OFFSET); + break; + case TOTEM_REMOTE_COMMAND_NEXT: + totem_action_next (totem); + break; + case TOTEM_REMOTE_COMMAND_PREVIOUS: + totem_action_previous (totem); + break; + case TOTEM_REMOTE_COMMAND_FULLSCREEN: + totem_action_fullscreen_toggle (totem); + break; + case TOTEM_REMOTE_COMMAND_QUIT: + totem_action_exit (totem); + break; + case TOTEM_REMOTE_COMMAND_ENQUEUE: + g_assert (url != NULL); + if (totem_playlist_add_mrl (totem->playlist, url, NULL) != FALSE) { + totem_action_add_recent (totem, url); + } + break; + case TOTEM_REMOTE_COMMAND_REPLACE: + g_assert (url != NULL); + totem_playlist_clear (totem->playlist); + if (strcmp (url, "dvd:") == 0) { + totem_action_play_media (totem, MEDIA_TYPE_DVD); + } else if (strcmp (url, "vcd:") == 0) { + totem_action_play_media (totem, MEDIA_TYPE_VCD); + } else if (g_str_has_prefix (url, "cd:") != FALSE) { + totem_action_play_media (totem, MEDIA_TYPE_CDDA); + } else if (g_str_has_prefix (url, "cdda:/") != FALSE) { + totem_playlist_add_mrl (totem->playlist, url, NULL); + } else if (totem_playlist_add_mrl (totem->playlist, + url, NULL) != FALSE) { + totem_action_add_recent (totem, url); + } + break; + case TOTEM_REMOTE_COMMAND_SHOW: + gtk_window_present (GTK_WINDOW (totem->win)); + break; + case TOTEM_REMOTE_COMMAND_TOGGLE_CONTROLS: + if (totem->controls_visibility != TOTEM_CONTROLS_FULLSCREEN) + { + GtkToggleAction *action; + gboolean state; + + action = GTK_TOGGLE_ACTION (gtk_action_group_get_action + (totem->main_action_group, + "show-controls")); + state = gtk_toggle_action_get_active (action); + gtk_toggle_action_set_active (action, !state); + } + break; + case TOTEM_REMOTE_COMMAND_SHOW_PLAYING: + { + char *title; + gboolean custom; + + title = totem_playlist_get_current_title + (totem->playlist, &custom); + bacon_message_connection_send (totem->conn, + title ? title : SHOW_PLAYING_NO_TRACKS); + g_free (title); + } + break; + case TOTEM_REMOTE_COMMAND_UP: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_UP); + break; + case TOTEM_REMOTE_COMMAND_DOWN: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_DOWN); + break; + case TOTEM_REMOTE_COMMAND_LEFT: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_LEFT); + break; + case TOTEM_REMOTE_COMMAND_RIGHT: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_RIGHT); + break; + case TOTEM_REMOTE_COMMAND_SELECT: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_SELECT); + break; + case TOTEM_REMOTE_COMMAND_DVD_MENU: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU); + break; + case TOTEM_REMOTE_COMMAND_ZOOM_UP: + totem_action_zoom_relative (totem, ZOOM_IN_OFFSET); + break; + case TOTEM_REMOTE_COMMAND_ZOOM_DOWN: + totem_action_zoom_relative (totem, ZOOM_OUT_OFFSET); + break; + case TOTEM_REMOTE_COMMAND_EJECT: + totem_action_eject (totem); + break; + case TOTEM_REMOTE_COMMAND_PLAY_DVD: + // TODO - how to see if can, and play the DVD (like the menu item) + break; + case TOTEM_REMOTE_COMMAND_MUTE: + totem_action_volume_relative (totem, -100); + break; + default: + handled = FALSE; + break; + } + + if (handled != FALSE && + gtk_window_is_active (GTK_WINDOW (totem->win))) { + on_video_motion_notify_event (NULL, NULL, totem); + } +} + +#ifdef HAVE_REMOTE +static void +totem_button_pressed_remote_cb (TotemRemote *remote, TotemRemoteCommand cmd, + Totem *totem) +{ + totem_action_remote (totem, cmd, NULL); +} +#endif /* HAVE_REMOTE */ + +static void +playlist_changed_cb (GtkWidget *playlist, Totem *totem) +{ + char *mrl; + + update_buttons (totem); + mrl = totem_playlist_get_current_mrl (totem->playlist); + + if (mrl == NULL) + return; + + //FIXME we shouldn't compare the 2 URLs + // http://bugzilla.gnome.org/show_bug.cgi?id=364311 + if (totem->mrl == NULL + || (totem->mrl != NULL && mrl != NULL + && strcmp (totem->mrl, mrl) != 0)) + { + totem_action_set_mrl_and_play (totem, mrl); + } else if (totem->mrl != NULL) { + totem_playlist_set_playing (totem->playlist, TRUE); + } + + g_free (mrl); +} + +static void +item_activated_cb (GtkWidget *playlist, Totem *totem) +{ + totem_action_seek (totem, 0); +} + +static void +current_removed_cb (GtkWidget *playlist, Totem *totem) +{ + char *mrl; + + /* Set play button status */ + play_pause_set_label (totem, STATE_STOPPED); + mrl = totem_playlist_get_current_mrl (totem->playlist); + + if (mrl == NULL) + { + totem_playlist_set_at_start (totem->playlist); + update_buttons (totem); + mrl = totem_playlist_get_current_mrl (totem->playlist); + } else { + update_buttons (totem); + } + + totem_action_set_mrl_and_play (totem, mrl); + g_free (mrl); +} + +static void +playlist_repeat_toggle_cb (TotemPlaylist *playlist, gboolean repeat, Totem *totem) +{ + GtkAction *action; + + action = gtk_action_group_get_action (totem->main_action_group, "repeat-mode"); + + g_signal_handlers_block_matched (G_OBJECT (action), G_SIGNAL_MATCH_DATA, 0, 0, + NULL, NULL, totem); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat); + + g_signal_handlers_unblock_matched (G_OBJECT (action), G_SIGNAL_MATCH_DATA, 0, 0, + NULL, NULL, totem); +} + +static void +playlist_shuffle_toggle_cb (TotemPlaylist *playlist, gboolean shuffle, Totem *totem) +{ + GtkAction *action; + + action = gtk_action_group_get_action (totem->main_action_group, "shuffle-mode"); + + g_signal_handlers_block_matched (G_OBJECT (action), G_SIGNAL_MATCH_DATA, 0, 0, + NULL, NULL, totem); + + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle); + + g_signal_handlers_unblock_matched (G_OBJECT (action), G_SIGNAL_MATCH_DATA, 0, 0, + NULL, NULL, totem); +} + +static void +update_fullscreen_size (Totem *totem) +{ + GdkScreen *screen; + + screen = gtk_window_get_screen (GTK_WINDOW (totem->win)); + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window + (screen, totem->win->window), + &totem->fullscreen_rect); +} + +gboolean +totem_is_fullscreen (Totem *totem) +{ + return (totem->controls_visibility == TOTEM_CONTROLS_FULLSCREEN); +} + +static void +move_popups (Totem *totem) +{ + int control_width, control_height; + int exit_width, exit_height; + + update_fullscreen_size (totem); + + gtk_window_get_size (GTK_WINDOW (totem->control_popup), + &control_width, &control_height); + gtk_window_get_size (GTK_WINDOW (totem->exit_popup), + &exit_width, &exit_height); + + /* We take the full width of the screen */ + gtk_window_resize (GTK_WINDOW (totem->control_popup), + totem->fullscreen_rect.width, + control_height); + + if (gtk_widget_get_direction (totem->exit_popup) == GTK_TEXT_DIR_RTL) + { + gtk_window_move (GTK_WINDOW (totem->exit_popup), + totem->fullscreen_rect.x, + totem->fullscreen_rect.y); + gtk_window_move (GTK_WINDOW (totem->control_popup), + totem->fullscreen_rect.width - control_width, + totem->fullscreen_rect.height + totem->fullscreen_rect.y - control_height); + } else { + gtk_window_move (GTK_WINDOW (totem->exit_popup), + totem->fullscreen_rect.width + totem->fullscreen_rect.x - exit_width, + totem->fullscreen_rect.y); + gtk_window_move (GTK_WINDOW (totem->control_popup), + totem->fullscreen_rect.x, + totem->fullscreen_rect.height + totem->fullscreen_rect.y - control_height); + } +} + +static void +size_changed_cb (GdkScreen *screen, Totem *totem) +{ + move_popups (totem); +} + +static void +theme_changed_cb (GtkIconTheme *icon_theme, Totem *totem) +{ + move_popups (totem); +} + +static void +window_realize_cb (GtkWidget *widget, Totem *totem) +{ + GdkScreen *screen; + + screen = gtk_widget_get_screen (widget); + g_signal_connect (G_OBJECT (screen), "size-changed", + G_CALLBACK (size_changed_cb), totem); + g_signal_connect (G_OBJECT (gtk_icon_theme_get_for_screen (screen)), + "changed", G_CALLBACK (theme_changed_cb), totem); +} + +static void +popup_timeout_add(Totem* totem) +{ + totem->popup_timeout = g_timeout_add (FULLSCREEN_POPUP_TIMEOUT, + (GSourceFunc) popup_hide, totem); +} + +static void +popup_timeout_remove (Totem *totem) +{ + if (totem->popup_timeout != 0) + { + g_source_remove (totem->popup_timeout); + totem->popup_timeout = 0; + } +} + +static gboolean +popup_hide (Totem *totem) +{ + if (totem->bvw == NULL || totem_is_fullscreen (totem) == FALSE) + return TRUE; + + if (totem->seek_lock != FALSE || totem->vol_fs_lock != FALSE) + return TRUE; + + gtk_widget_hide (GTK_WIDGET (totem->exit_popup)); + gtk_widget_hide (GTK_WIDGET (totem->control_popup)); + + popup_timeout_remove (totem); + + totem_action_set_cursor (totem, FALSE); + + return FALSE; +} + +static void +on_mouse_click_fullscreen (GtkWidget *widget, Totem *totem) +{ + popup_timeout_remove (totem); + popup_timeout_add (totem); +} + +static gboolean +on_video_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, + Totem *totem) +{ + GtkWidget *item; + + if (totem_is_fullscreen (totem) == FALSE) + return FALSE; + + if (totem->popup_in_progress != FALSE) + return FALSE; + + totem->popup_in_progress = TRUE; + + popup_timeout_remove (totem); + + item = glade_xml_get_widget (totem->xml, "tcw_hbox"); + gtk_widget_show_all (item); + gdk_flush (); + + move_popups (totem); + + gtk_widget_show_all (totem->exit_popup); + gtk_widget_show_all (totem->control_popup); + totem_action_set_cursor (totem, TRUE); + + popup_timeout_add (totem); + totem->popup_in_progress = FALSE; + + return FALSE; +} + +static gboolean +on_video_button_press_event (BaconVideoWidget *bvw, GdkEventButton *event, + Totem *totem) +{ + if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + totem_action_fullscreen_toggle(totem); + return TRUE; + } else if (event->type == GDK_BUTTON_PRESS && event->button == 2) { + totem_action_play_pause(totem); + return TRUE; + } else if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + totem_action_menu_popup (totem, event->button); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_eos_event (GtkWidget *widget, Totem *totem) +{ + if (bacon_video_widget_get_logo_mode (totem->bvw) != FALSE) + return FALSE; + + if (totem_playlist_has_next_mrl (totem->playlist) == FALSE + && totem_playlist_get_repeat (totem->playlist) == FALSE) + { + char *mrl; + + /* Set play button status */ + play_pause_set_label (totem, STATE_PAUSED); + totem_playlist_set_at_start (totem->playlist); + update_buttons (totem); + mrl = totem_playlist_get_current_mrl (totem->playlist); + totem_action_stop (totem); + totem_action_set_mrl_with_warning (totem, mrl, FALSE); + bacon_video_widget_pause (totem->bvw); + g_free (mrl); + } else { + totem_action_next (totem); + } + + return FALSE; +} + +static gboolean +totem_action_handle_key_release (Totem *totem, GdkEventKey *event) +{ + gboolean retval = TRUE; + + switch (event->keyval) { + case GDK_Left: + case GDK_Right: + totem_statusbar_set_seeking + (TOTEM_STATUSBAR (totem->statusbar), FALSE); + break; + } + + return retval; +} + +static void +totem_action_handle_seek (Totem *totem, GdkEventKey *event, gboolean is_forward) +{ + if (is_forward != FALSE) { + if (event->state & GDK_SHIFT_MASK) { + totem_action_seek_relative (totem, + SEEK_FORWARD_SHORT_OFFSET); + } else if (event->state & GDK_CONTROL_MASK) { + totem_action_seek_relative (totem, + SEEK_FORWARD_LONG_OFFSET); + } else { + totem_action_seek_relative (totem, + SEEK_FORWARD_OFFSET); + } + } else { + if (event->state & GDK_SHIFT_MASK) { + totem_action_seek_relative (totem, + SEEK_BACKWARD_SHORT_OFFSET); + } else if (event->state & GDK_CONTROL_MASK) { + totem_action_seek_relative (totem, + SEEK_BACKWARD_LONG_OFFSET); + } else { + totem_action_seek_relative (totem, + SEEK_BACKWARD_OFFSET); + } + } +} + +static gboolean +totem_action_handle_key_press (Totem *totem, GdkEventKey *event) +{ + gboolean retval = TRUE, playlist_focused = FALSE; + GtkWidget *focused; + + focused = gtk_window_get_focus (GTK_WINDOW (totem->win)); + if (focused != NULL && gtk_widget_is_ancestor + (focused, GTK_WIDGET (totem->playlist)) != FALSE) { + playlist_focused = TRUE; + } + + switch (event->keyval) { + case GDK_A: + case GDK_a: + totem_action_toggle_aspect_ratio (totem); + break; +#ifdef HAVE_XFREE + case XF86XK_AudioPrev: +#endif /* HAVE_XFREE */ + case GDK_B: + case GDK_b: + totem_action_previous (totem); + break; + case GDK_C: + case GDK_c: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_CHAPTER_MENU); + break; +#ifndef HAVE_GTK_ONLY + case GDK_D: + case GDK_d: + totem_gromit_toggle (); + break; + case GDK_E: + case GDK_e: + totem_gromit_clear (FALSE); + break; +#endif /* !HAVE_GTK_ONLY */ + case GDK_F11: + case GDK_f: + case GDK_F: + totem_action_fullscreen_toggle (totem); + break; + case GDK_g: + case GDK_G: + if (totem_playing_dvd (totem->mrl) != FALSE) { + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_NEXT_ANGLE); + } + break; + case GDK_h: + case GDK_H: + totem_action_toggle_controls (totem); + break; + case GDK_i: + case GDK_I: + { + GtkToggleAction *action; + gboolean state; + + action = GTK_TOGGLE_ACTION (gtk_action_group_get_action + (totem->main_action_group, + "deinterlace")); + state = gtk_toggle_action_get_active (action); + gtk_toggle_action_set_active (action, !state); + } + break; + case GDK_M: + case GDK_m: + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU); + break; +#ifdef HAVE_XFREE + case XF86XK_AudioNext: +#endif /* HAVE_XFREE */ + case GDK_N: + case GDK_n: + totem_action_next (totem); + break; + case GDK_O: + case GDK_o: + totem_action_fullscreen (totem, FALSE); + totem_action_open (totem); + break; +#ifdef HAVE_XFREE + case XF86XK_AudioPlay: + case XF86XK_AudioPause: +#endif /* HAVE_XFREE */ + case GDK_p: + case GDK_P: + if (event->state & GDK_CONTROL_MASK) + totem_action_show_properties (totem); + else + totem_action_play_pause (totem); + break; + case GDK_q: + case GDK_Q: + totem_action_exit (totem); + break; + case GDK_r: + case GDK_R: + totem_action_zoom_relative (totem, ZOOM_IN_OFFSET); + break; + case GDK_s: + case GDK_S: + if (event->state & GDK_CONTROL_MASK) { + totem_action_take_screenshot (totem); + } else { + totem_action_skip_to (totem); + } + break; + case GDK_t: + case GDK_T: + totem_action_zoom_relative (totem, ZOOM_OUT_OFFSET); + break; + case GDK_Escape: + if (event->state & GDK_SUPER_MASK) + bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU); + else + totem_action_fullscreen (totem, FALSE); + break; + case GDK_Left: + if (playlist_focused != FALSE) + return FALSE; + + totem_statusbar_set_seeking + (TOTEM_STATUSBAR (totem->statusbar), TRUE); + if (gtk_widget_get_direction (totem->win) == GTK_TEXT_DIR_RTL) + totem_action_handle_seek (totem, event, TRUE); + else + totem_action_handle_seek (totem, event, FALSE); + break; + case GDK_Right: + if (playlist_focused != FALSE) + return FALSE; + + totem_statusbar_set_seeking + (TOTEM_STATUSBAR (totem->statusbar), TRUE); + if (gtk_widget_get_direction (totem->win) == GTK_TEXT_DIR_RTL) + totem_action_handle_seek (totem, event, FALSE); + else + totem_action_handle_seek (totem, event, TRUE); + break; + case GDK_space: + if (totem_is_fullscreen (totem) != FALSE || gtk_widget_is_focus (GTK_WIDGET (totem->bvw)) != FALSE) + totem_action_play_pause (totem); + else + retval = FALSE; + break; + case GDK_Up: + if (playlist_focused != FALSE) + return FALSE; + totem_action_volume_relative (totem, VOLUME_UP_OFFSET); + break; + case GDK_Down: + if (playlist_focused != FALSE) + return FALSE; + totem_action_volume_relative (totem, VOLUME_DOWN_OFFSET); + break; + case GDK_0: + if (event->state & GDK_CONTROL_MASK) + totem_action_zoom_reset (totem); + else + totem_action_set_scale_ratio (totem, 0.5); + break; + case GDK_onehalf: + totem_action_set_scale_ratio (totem, 0.5); + break; + case GDK_1: + totem_action_set_scale_ratio (totem, 1); + break; + case GDK_2: + totem_action_set_scale_ratio (totem, 2); + break; + case GDK_Menu: + if (playlist_focused != FALSE) + return FALSE; + totem_action_menu_popup (totem, 0); + break; + case GDK_F10: + if (playlist_focused != FALSE) + return FALSE; + if (!(event->state & GDK_SHIFT_MASK)) + return FALSE; + + totem_action_menu_popup (totem, 0); + break; + case GDK_plus: + case GDK_KP_Add: + if (!(event->state & GDK_CONTROL_MASK)) + return FALSE; + + totem_action_zoom_relative (totem, ZOOM_IN_OFFSET); + break; + case GDK_minus: + case GDK_KP_Subtract: + if (!(event->state & GDK_CONTROL_MASK)) + return FALSE; + + totem_action_zoom_relative (totem, ZOOM_OUT_OFFSET); + break; + case GDK_KP_Up: + case GDK_KP_8: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_UP); + break; + case GDK_KP_Down: + case GDK_KP_2: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_DOWN); + break; + case GDK_KP_Right: + case GDK_KP_6: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_RIGHT); + break; + case GDK_KP_Left: + case GDK_KP_4: + bacon_video_widget_dvd_event (totem->bvw, + BVW_DVD_ROOT_MENU_LEFT); + break; + default: + retval = FALSE; + } + + return retval; +} + +static gboolean +totem_action_handle_scroll (Totem *totem, GdkScrollDirection direction) +{ + gboolean retval = TRUE; + + on_video_motion_notify_event (NULL, NULL, totem); + + switch (direction) { + case GDK_SCROLL_UP: + totem_action_seek_relative + (totem, SEEK_FORWARD_SHORT_OFFSET); + break; + case GDK_SCROLL_DOWN: + totem_action_seek_relative + (totem, SEEK_BACKWARD_SHORT_OFFSET); + break; + default: + retval = FALSE; + } + + return retval; +} + +static gboolean +totem_action_handle_volume_scroll (Totem *totem, GdkScrollDirection direction) +{ + gboolean retval = TRUE; + + on_video_motion_notify_event (NULL, NULL, totem); + + switch (direction) { + case GDK_SCROLL_UP: + totem_action_volume_relative (totem, VOLUME_UP_OFFSET); + break; + case GDK_SCROLL_DOWN: + totem_action_volume_relative (totem, VOLUME_DOWN_OFFSET); + break; + default: + retval = FALSE; + } + + return retval; +} + +static int +on_window_key_press_event (GtkWidget *win, GdkEventKey *event, Totem *totem) +{ + /* Special case Eject, Open, Open URI and + * seeking keyboard shortcuts */ + if (event->state != 0 + && (event->state & GDK_CONTROL_MASK)) + { + switch (event->keyval) + case GDK_E: + case GDK_e: + case GDK_O: + case GDK_o: + case GDK_L: + case GDK_l: + case GDK_q: + case GDK_Q: + case GDK_S: + case GDK_s: + case GDK_Right: + case GDK_Left: + case GDK_plus: + case GDK_KP_Add: + case GDK_minus: + case GDK_KP_Subtract: + case GDK_0: + if (event->type == GDK_KEY_PRESS) { + return totem_action_handle_key_press (totem, event); + } else { + return totem_action_handle_key_release (totem, event); + } + } + + if (event->state != 0 + && (event->state & GDK_SUPER_MASK)) { + switch (event->keyval) + case GDK_Escape: + if (event->type == GDK_KEY_PRESS) { + return totem_action_handle_key_press (totem, event); + } else { + return totem_action_handle_key_release (totem, event); + } + } + + + /* If we have modifiers, and either Ctrl, Mod1 (Alt), or any + * of Mod3 to Mod5 (Mod2 is num-lock...) are pressed, we + * let Gtk+ handle the key */ + if (event->state != 0 + && ((event->state & GDK_CONTROL_MASK) + || (event->state & GDK_MOD1_MASK) + || (event->state & GDK_MOD3_MASK) + || (event->state & GDK_MOD4_MASK) + || (event->state & GDK_MOD5_MASK))) + return FALSE; + + if (event->type == GDK_KEY_PRESS) { + return totem_action_handle_key_press (totem, event); + } else { + return totem_action_handle_key_release (totem, event); + } +} + +static int +on_window_scroll_event (GtkWidget *win, GdkEventScroll *event, Totem *totem) +{ + return totem_action_handle_scroll (totem, event->direction); +} + +static int +on_volume_scroll_event (GtkWidget *win, GdkEventScroll *event, Totem *totem) +{ + return totem_action_handle_volume_scroll (totem, event->direction); +} + +#ifdef HAVE_MEDIA_PLAYER_KEYS +static gboolean +on_window_focus_in_event (GtkWidget *win, GdkEventFocus *event, Totem *totem) +{ + if (totem->remote != NULL) { + totem_remote_window_activated (totem->remote); + } + + return FALSE; +} +#endif + +static void +update_media_menu_items (Totem *totem) +{ + gboolean playing; + + playing = totem_playing_dvd (totem->mrl); + + totem_action_set_sensitivity ("dvd-root-menu", playing); + totem_action_set_sensitivity ("dvd-title-menu", playing); + totem_action_set_sensitivity ("dvd-audio-menu", playing); + totem_action_set_sensitivity ("dvd-angle-menu", playing); + totem_action_set_sensitivity ("dvd-chapter-menu", playing); + /* FIXME we should only show that if we have multiple angles */ + totem_action_set_sensitivity ("next-angle", playing); + + playing = totem_is_media (totem->mrl); + totem_action_set_sensitivity ("eject", playing); +} + +static void +update_buttons (Totem *totem) +{ + gboolean has_item; + + /* Previous */ + if (totem_playing_dvd (totem->mrl) != FALSE) + has_item = bacon_video_widget_has_previous_track (totem->bvw); + else + has_item = totem_playlist_has_previous_mrl (totem->playlist); + + totem_action_set_sensitivity ("previous-chapter", has_item); + + /* Next */ + if (totem_playing_dvd (totem->mrl) != FALSE) + has_item = bacon_video_widget_has_next_track (totem->bvw); + else + has_item = totem_playlist_has_next_mrl (totem->playlist); + + totem_action_set_sensitivity ("next-chapter", has_item); +} + +static void +main_pane_size_allocated (GtkWidget *main_pane, GtkAllocation *allocation, Totem *totem) +{ + gulong handler_id; + + if (!totem->maximised || GTK_WIDGET_MAPPED (totem->win)) { + handler_id = g_signal_handler_find (main_pane, + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, + 0, 0, NULL, + main_pane_size_allocated, totem); + g_signal_handler_disconnect (main_pane, handler_id); + + gtk_paned_set_position (GTK_PANED (main_pane), allocation->width - totem->sidebar_w); + } +} + +static void +totem_setup_window (Totem *totem) +{ + GKeyFile *keyfile; + int w, h; + gboolean show_sidebar; + char *filename, *page_id; + GError *err = NULL; + GtkWidget *main_pane; + + filename = g_build_filename (g_get_home_dir (), ".gnome2", "totem", NULL); + keyfile = g_key_file_new (); + if (g_key_file_load_from_file (keyfile, filename, + G_KEY_FILE_NONE, NULL) == FALSE) { + totem->sidebar_w = w = h = 0; + show_sidebar = TRUE; + page_id = NULL; + g_free (filename); + } else { + g_free (filename); + + w = g_key_file_get_integer (keyfile, "State", "window_w", &err); + if (err != NULL) { + w = 0; + g_error_free (err); + err = NULL; + } + + h = g_key_file_get_integer (keyfile, "State", "window_h", &err); + if (err != NULL) { + h = 0; + g_error_free (err); + err = NULL; + } + + show_sidebar = g_key_file_get_boolean (keyfile, "State", + "show_sidebar", &err); + if (err != NULL) { + show_sidebar = TRUE; + g_error_free (err); + err = NULL; + } + + totem->maximised = g_key_file_get_boolean (keyfile, "State", + "maximised", &err); + if (err != NULL) { + g_error_free (err); + err = NULL; + } + + page_id = g_key_file_get_string (keyfile, "State", + "sidebar_page", &err); + if (err != NULL) { + g_error_free (err); + page_id = NULL; + err = NULL; + } + + totem->sidebar_w = g_key_file_get_integer (keyfile, "State", + "sidebar_w", &err); + if (err != NULL) { + g_error_free (err); + totem->sidebar_w = 0; + } + g_key_file_free (keyfile); + } + + if (w > 0 && h > 0 && totem->maximised == FALSE) { + gtk_window_set_default_size (GTK_WINDOW (totem->win), + w, h); + totem->window_w = w; + totem->window_h = h; + } else if (totem->maximised != FALSE) { + gtk_window_maximize (GTK_WINDOW (totem->win)); + } + + main_pane = glade_xml_get_widget (totem->xml, "tmw_main_pane"); + g_signal_connect (G_OBJECT (main_pane), "size-allocate", G_CALLBACK (main_pane_size_allocated), totem); + + totem_sidebar_setup (totem, show_sidebar, page_id); +} + +static void +totem_callback_connect (Totem *totem) +{ + GtkWidget *item, *box, *arrow; + GtkAction *action; + + /* Menu items */ + gtk_action_group_set_visible (totem->zoom_action_group, + bacon_video_widget_can_set_zoom (totem->bvw)); + + action = gtk_action_group_get_action (totem->main_action_group, "repeat-mode"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + totem_playlist_get_repeat (totem->playlist)); + action = gtk_action_group_get_action (totem->main_action_group, "shuffle-mode"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + totem_playlist_get_shuffle (totem->playlist)); + + /* Controls */ + box = glade_xml_get_widget (totem->xml, "tmw_buttons_hbox"); + + action = gtk_action_group_get_action (totem->main_action_group, + "previous-chapter"); + item = gtk_action_create_tool_item (action); + atk_object_set_name (gtk_widget_get_accessible (item), + _("Previous Chapter/Movie")); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + + action = gtk_action_group_get_action (totem->main_action_group, "play"); + item = gtk_action_create_tool_item (action); + atk_object_set_name (gtk_widget_get_accessible (item), + _("Play / Pause")); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + + action = gtk_action_group_get_action (totem->main_action_group, + "next-chapter"); + item = gtk_action_create_tool_item (action); + atk_object_set_name (gtk_widget_get_accessible (item), + _("Next Chapter/Movie")); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + + /* Sidebar button (Drag'n'Drop) */ + box = glade_xml_get_widget (totem->xml, "tmw_sidebar_button_hbox"); + action = gtk_action_group_get_action (totem->main_action_group, "sidebar"); + item = gtk_toggle_button_new (); + gtk_action_connect_proxy (action, item); + arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE); + gtk_widget_show (arrow); + gtk_button_set_image (GTK_BUTTON (item), arrow); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (item), "drag_data_received", + G_CALLBACK (drop_playlist_cb), totem); + gtk_drag_dest_set (item, GTK_DEST_DEFAULT_ALL, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + /* Main Window */ + g_signal_connect (G_OBJECT (totem->win), "delete-event", + G_CALLBACK (main_window_destroy_cb), totem); + g_object_notify (G_OBJECT (totem->win), "is-active"); + g_signal_connect_swapped (G_OBJECT (totem->win), "notify", + G_CALLBACK (popup_hide), totem); + g_signal_connect (G_OBJECT (totem->win), "window-state-event", + G_CALLBACK (window_state_event_cb), totem); + + /* Screen size and Theme changes */ + g_signal_connect (totem->win, "realize", + G_CALLBACK (window_realize_cb), totem); + + /* Motion notify for the Popups */ + item = glade_xml_get_widget (totem->xml, + "totem_exit_fullscreen_window"); + gtk_widget_add_events (item, GDK_POINTER_MOTION_MASK); + g_signal_connect (G_OBJECT (item), "motion-notify-event", + G_CALLBACK (on_video_motion_notify_event), totem); + item = glade_xml_get_widget (totem->xml, "totem_controls_window"); + gtk_widget_add_events (item, GDK_POINTER_MOTION_MASK); + g_signal_connect (G_OBJECT (item), "motion-notify-event", + G_CALLBACK (on_video_motion_notify_event), totem); + + /* Popup */ + item = glade_xml_get_widget (totem->xml, "tefw_fs_exit_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_fs_exit1_activate), totem); + g_signal_connect (G_OBJECT (item), "motion-notify-event", + G_CALLBACK (on_video_motion_notify_event), totem); + + /* Control Popup */ + box = glade_xml_get_widget (totem->xml, "tcw_buttons_hbox"); + + action = gtk_action_group_get_action (totem->main_action_group, "previous-chapter"); + item = gtk_action_create_tool_item (action); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_mouse_click_fullscreen), totem); + + action = gtk_action_group_get_action (totem->main_action_group, "play"); + item = gtk_action_create_tool_item (action); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_mouse_click_fullscreen), totem); + + action = gtk_action_group_get_action (totem->main_action_group, "next-chapter"); + item = gtk_action_create_tool_item (action); + gtk_box_pack_start (GTK_BOX (box), item, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_mouse_click_fullscreen), totem); + + + item = glade_xml_get_widget (totem->xml, "tcw_volume_mute_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_volume_mute_button), totem); + item = glade_xml_get_widget (totem->xml, "tcw_volume_max_button"); + g_signal_connect (G_OBJECT (item), "clicked", + G_CALLBACK (on_volume_max_button), totem); + + /* Control Popup Sliders */ + g_signal_connect (G_OBJECT(totem->fs_seek), "button_press_event", + G_CALLBACK (seek_slider_pressed_cb), totem); + g_signal_connect (G_OBJECT(totem->fs_seek), "button_release_event", + G_CALLBACK (seek_slider_released_cb), totem); + g_signal_connect (G_OBJECT (totem->fs_seekadj), "value-changed", + G_CALLBACK (seek_slider_changed_cb), totem); + g_signal_connect (G_OBJECT(totem->fs_volume), "value-changed", + G_CALLBACK (vol_cb), totem); + g_signal_connect (G_OBJECT(totem->fs_volume), "button_press_event", + G_CALLBACK (vol_slider_pressed_cb), totem); + g_signal_connect (G_OBJECT(totem->fs_volume), "button_release_event", + G_CALLBACK (vol_slider_released_cb), totem); + + /* Connect the keys */ + gtk_widget_add_events (totem->win, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); + g_signal_connect (G_OBJECT(totem->win), "key_press_event", + G_CALLBACK (on_window_key_press_event), totem); + g_signal_connect (G_OBJECT(totem->win), "key_release_event", + G_CALLBACK (on_window_key_press_event), totem); + + /* Connect the mouse wheel */ + gtk_widget_add_events (totem->win, GDK_SCROLL_MASK); + g_signal_connect (G_OBJECT(totem->win), "scroll_event", + G_CALLBACK (on_window_scroll_event), totem); + gtk_widget_add_events (totem->seek, GDK_SCROLL_MASK); + g_signal_connect (G_OBJECT (totem->seek), "scroll_event", + G_CALLBACK (on_window_scroll_event), totem); + gtk_widget_add_events (totem->fs_seek, GDK_SCROLL_MASK); + g_signal_connect (G_OBJECT (totem->fs_seek), "scroll_event", + G_CALLBACK (on_window_scroll_event), totem); + gtk_widget_add_events (totem->volume, GDK_SCROLL_MASK); + g_signal_connect (G_OBJECT (totem->volume), "scroll_event", + G_CALLBACK (on_volume_scroll_event), totem); + gtk_widget_add_events (totem->fs_volume, GDK_SCROLL_MASK); + g_signal_connect (G_OBJECT (totem->fs_volume), "scroll_event", + G_CALLBACK (on_volume_scroll_event), totem); + + /* Sliders */ + g_signal_connect (G_OBJECT (totem->seek), "button_press_event", + G_CALLBACK (seek_slider_pressed_cb), totem); + g_signal_connect (G_OBJECT (totem->seek), "button_release_event", + G_CALLBACK (seek_slider_released_cb), totem); + g_signal_connect (G_OBJECT (totem->seekadj), "value_changed", + G_CALLBACK (seek_slider_changed_cb), totem); + g_signal_connect (G_OBJECT (totem->volume), "value-changed", + G_CALLBACK (vol_cb), totem); + + /* Set sensitivity of the toolbar buttons */ + totem_action_set_sensitivity ("play", FALSE); + totem_action_set_sensitivity ("next-chapter", FALSE); + totem_action_set_sensitivity ("previous-chapter", FALSE); + +#ifdef HAVE_MEDIA_PLAYER_KEYS + g_signal_connect (G_OBJECT(totem->win), "focus-in-event", + G_CALLBACK (on_window_focus_in_event), totem); +#endif +} + +static void +playlist_widget_setup (Totem *totem) +{ + totem->playlist = TOTEM_PLAYLIST (totem_playlist_new ()); + + if (totem->playlist == NULL) + totem_action_exit (totem); + + gtk_widget_show_all (GTK_WIDGET (totem->playlist)); + + g_signal_connect (G_OBJECT (totem->playlist), "active-name-changed", + G_CALLBACK (on_playlist_change_name), totem); + g_signal_connect (G_OBJECT (totem->playlist), "item-activated", + G_CALLBACK (item_activated_cb), totem); + g_signal_connect (G_OBJECT (totem->playlist), + "changed", G_CALLBACK (playlist_changed_cb), + totem); + g_signal_connect (G_OBJECT (totem->playlist), + "current-removed", G_CALLBACK (current_removed_cb), + totem); + g_signal_connect (G_OBJECT (totem->playlist), + "repeat-toggled", + G_CALLBACK (playlist_repeat_toggle_cb), + totem); + g_signal_connect (G_OBJECT (totem->playlist), + "shuffle-toggled", + G_CALLBACK (playlist_shuffle_toggle_cb), + totem); + +} + +static void +video_widget_create (Totem *totem) +{ + GError *err = NULL; + GtkWidget *container; + BaconVideoWidget **bvw; + + totem->scr = totem_scrsaver_new (); + + totem->bvw = BACON_VIDEO_WIDGET + (bacon_video_widget_new (-1, -1, BVW_USE_TYPE_VIDEO, &err)); + + if (totem->bvw == NULL) + { + totem_playlist_set_playing (totem->playlist, FALSE); + + gtk_widget_hide (totem->win); + + totem_action_error_and_exit (_("Totem could not startup."), err != NULL ? err->message : _("No reason."), totem); + if (err != NULL) + g_error_free (err); + } + + totem_preferences_tvout_setup (totem); + totem_preferences_visuals_setup (totem); + totem_action_zoom (totem, ZOOM_RESET); + + g_signal_connect (G_OBJECT (totem->bvw), + "motion-notify-event", + G_CALLBACK (on_video_motion_notify_event), + totem); + g_signal_connect_after (G_OBJECT (totem->bvw), + "button-press-event", + G_CALLBACK (on_video_button_press_event), + totem); + g_signal_connect (G_OBJECT (totem->bvw), + "eos", + G_CALLBACK (on_eos_event), + totem); + g_signal_connect (G_OBJECT (totem->bvw), + "got-redirect", + G_CALLBACK (on_got_redirect), + totem); + g_signal_connect (G_OBJECT(totem->bvw), + "title-change", + G_CALLBACK (on_title_change_event), + totem); + g_signal_connect (G_OBJECT(totem->bvw), + "channels-change", + G_CALLBACK (on_channels_change_event), + totem); + g_signal_connect (G_OBJECT (totem->bvw), + "tick", + G_CALLBACK (update_current_time), + totem); + g_signal_connect (G_OBJECT (totem->bvw), + "got-metadata", + G_CALLBACK (on_got_metadata_event), + totem); + g_signal_connect (G_OBJECT (totem->bvw), + "buffering", + G_CALLBACK (on_buffering_event), + totem); + g_signal_connect (G_OBJECT (totem->bvw), + "error", + G_CALLBACK (on_error_event), + totem); + + totem_missing_plugins_setup (totem); + + container = glade_xml_get_widget (totem->xml, "tmw_bvw_vbox"); + gtk_container_add (GTK_CONTAINER (container), + GTK_WIDGET (totem->bvw)); + + /* Events for the widget video window as well */ + gtk_widget_add_events (GTK_WIDGET (totem->bvw), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); + g_signal_connect (G_OBJECT(totem->bvw), "key_press_event", + G_CALLBACK (on_window_key_press_event), totem); + g_signal_connect (G_OBJECT(totem->bvw), "key_release_event", + G_CALLBACK (on_window_key_press_event), totem); + + g_signal_connect (G_OBJECT (totem->bvw), "drag_data_received", + G_CALLBACK (drop_video_cb), totem); + gtk_drag_dest_set (GTK_WIDGET (totem->bvw), GTK_DEST_DEFAULT_ALL, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (G_OBJECT (totem->bvw), "drag_data_get", + G_CALLBACK (drag_video_cb), totem); + gtk_drag_source_set (GTK_WIDGET (totem->bvw), + GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + source_table, G_N_ELEMENTS (source_table), + GDK_ACTION_LINK); + + bvw = &(totem->bvw); + g_object_add_weak_pointer (G_OBJECT (totem->bvw), + (gpointer *) bvw); + + gtk_widget_show (GTK_WIDGET (totem->bvw)); + + bacon_video_widget_set_volume (totem->bvw, + gconf_client_get_int (totem->gc, + GCONF_PREFIX"/volume", NULL)); + g_signal_connect (G_OBJECT (totem->bvw), "notify", + G_CALLBACK (property_notify_cb), totem); + update_volume_sliders (totem); +} + +static void +totem_message_connection_receive_cb (const char *msg, Totem *totem) +{ + char *command_str, *url; + int command; + + if (strlen (msg) < 4) + return; + + command_str = g_strndup (msg, 3); + sscanf (command_str, "%d", &command); + g_free (command_str); + + if (msg[4] != '\0') + url = g_strdup (msg + 4); + else + url = NULL; + + totem_action_remote (totem, command, url); + + g_free (url); +} + +GtkWidget * +totem_volume_create (void) +{ + GtkWidget *widget; + + widget = bacon_volume_button_new (GTK_ICON_SIZE_SMALL_TOOLBAR, + 0, 100, 1); + gtk_widget_set_sensitive (widget, FALSE); + gtk_widget_show (widget); + + return widget; +} + +int +main (int argc, char **argv) +{ + Totem *totem; + GConfClient *gc; +#ifndef HAVE_GTK_ONLY + GnomeProgram *program; +#else + GError *error = NULL; +#endif + GOptionContext *context; + GOptionGroup *baconoptiongroup; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + +#ifdef GDK_WINDOWING_X11 + if (XInitThreads () == 0) + { + gtk_init (&argc, &argv); + g_set_application_name (_("Totem Movie Player")); + totem_action_error_and_exit (_("Could not initialize the thread-safe libraries."), _("Verify your system installation. Totem will now exit."), NULL); + } +#endif + + g_thread_init (NULL); + + /* Handle command line arguments */ + context = g_option_context_new (_("- Play movies and songs")); + baconoptiongroup = bacon_video_widget_get_option_group(); + g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE); + g_option_context_add_group (context, baconoptiongroup); + +#ifdef HAVE_GTK_ONLY + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) { + totem_action_error_and_exit (_("Totem could not parse the command-line options"), error->message, NULL); + } +#else + program = gnome_program_init (PACKAGE, VERSION, + LIBGNOMEUI_MODULE, + argc, argv, + GNOME_PARAM_APP_DATADIR, DATADIR, + GNOME_PARAM_GOPTION_CONTEXT, context, + GNOME_PARAM_NONE); +#endif /* HAVE_GTK_ONLY */ + + g_set_application_name (_("Totem Movie Player")); + gtk_window_set_default_icon_name ("totem"); + + gnome_vfs_init (); + + gc = gconf_client_get_default (); + if (gc == NULL) + { + totem_action_error_and_exit (_("Totem could not initialize the configuration engine."), _("Make sure that GNOME is properly installed."), NULL); + } + +#ifndef HAVE_GTK_ONLY + gnome_authentication_manager_init (); +#endif /* !HAVE_GTK_ONLY */ + + totem = g_new0 (Totem, 1); + + /* IPC stuff */ + totem->conn = bacon_message_connection_new (GETTEXT_PACKAGE); + if (bacon_message_connection_get_is_server (totem->conn) == FALSE) + { + totem_options_process_for_server (totem->conn, &optionstate); + bacon_message_connection_free (totem->conn); + g_free (totem); + gdk_notify_startup_complete (); + exit (0); + } else { + totem_options_process_early (gc, &optionstate); + } + + /* Init totem itself */ + totem->prev_volume = -1; + totem->gc = gc; + totem->cursor_shown = TRUE; + + /* Main window */ + totem->xml = totem_interface_load ("totem.glade", _("main window"), + TRUE, NULL); + if (totem->xml == NULL) + totem_action_exit (NULL); + + totem->win = glade_xml_get_widget (totem->xml, "totem_main_window"); + + /* Menubar */ + totem_ui_manager_setup (totem); + + /* The sidebar */ + playlist_widget_setup (totem); + totem->properties = bacon_video_widget_properties_new (); + + /* The rest of the widgets */ + totem->state = STATE_STOPPED; + totem->seek = glade_xml_get_widget (totem->xml, "tmw_seek_hscale"); + totem->seekadj = gtk_range_get_adjustment (GTK_RANGE (totem->seek)); + g_object_set_data (G_OBJECT (totem->seek), "fs", GINT_TO_POINTER (0)); + totem->volume = glade_xml_get_widget (totem->xml, "tcw_volume_button"); + g_object_set_data (G_OBJECT (totem->volume), "fs", GINT_TO_POINTER (0)); + totem->exit_popup = glade_xml_get_widget + (totem->xml, "totem_exit_fullscreen_window"); + totem->control_popup = glade_xml_get_widget + (totem->xml, "totem_controls_window"); + totem->fs_seek = glade_xml_get_widget (totem->xml, "tcw_seek_hscale"); + totem->fs_seekadj = gtk_range_get_adjustment + (GTK_RANGE (totem->fs_seek)); + g_object_set_data (G_OBJECT (totem->fs_seek), "fs", GINT_TO_POINTER (1)); + totem->fs_volume = glade_xml_get_widget + (totem->xml, "tcw_volume_hscale"); + totem->fs_voladj = gtk_range_get_adjustment + (GTK_RANGE (totem->fs_volume)); + totem->tooltip = gtk_tooltips_new (); + g_object_set_data (G_OBJECT (totem->fs_volume), "fs", GINT_TO_POINTER (1)); + totem->volume_first_time = 1; + totem->statusbar = glade_xml_get_widget (totem->xml, "tmw_statusbar"); + totem->tcw_time_label = glade_xml_get_widget (totem->xml, + "tcw_time_display_label"); + totem->seek_lock = totem->vol_lock = totem->vol_fs_lock = FALSE; + + totem_session_setup (totem, argv); + totem_setup_recent (totem); + totem_setup_file_monitoring (totem); + totem_setup_file_filters (); + totem_setup_play_disc (totem); + totem_callback_connect (totem); + totem_setup_window (totem); + + /* Show ! gtk_main_iteration trickery to show all the widgets + * we have so far */ + gtk_widget_show (totem->win); + totem_gdk_window_set_waiting_cursor (totem->win->window); + update_fullscreen_size (totem); + long_action (); + + totem->controls_visibility = TOTEM_CONTROLS_VISIBLE; + + /* Show ! (again) the video widget this time. */ + video_widget_create (totem); + long_action (); + gtk_widget_grab_focus (GTK_WIDGET (totem->bvw)); + bacon_video_widget_set_logo (totem->bvw, LOGO_PATH); + + /* The prefs after the video widget is connected */ + totem_setup_preferences (totem); + + /* Command-line handling */ + totem_options_process_late (totem, &optionstate); + + if (totem->session_restored != FALSE) + { + totem_session_restore (totem, optionstate.filenames); + } else if (optionstate.filenames != NULL && totem_action_open_files (totem, optionstate.filenames)) { + totem_action_play_pause (totem); + } else { + totem_action_set_mrl (totem, NULL); + } + gdk_window_set_cursor (totem->win->window, NULL); + + if (bacon_message_connection_get_is_server (totem->conn) != FALSE) + { + bacon_message_connection_set_callback (totem->conn, + (BaconMessageReceivedFunc) + totem_message_connection_receive_cb, totem); + } + +#ifdef HAVE_REMOTE + totem->remote = totem_remote_new (); + g_signal_connect (totem->remote, "button_pressed", + G_CALLBACK (totem_button_pressed_remote_cb), totem); +#endif /* HAVE_REMOTE */ + + gtk_main (); + +#ifndef HAVE_GTK_ONLY + /* Will destroy GOption allocated data automatically */ + g_object_unref (program); +#endif + return 0; +} diff --git a/trunk/src/totem.h b/trunk/src/totem.h new file mode 100644 index 000000000..384a0d5bf --- /dev/null +++ b/trunk/src/totem.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2001,2002,2003,2004,2005 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef __TOTEM_H__ +#define __TOTEM_H__ + +#include <gconf/gconf-client.h> +#include <gtk/gtkwidget.h> +#include "bacon-video-widget.h" + +#define TOTEM_GCONF_PREFIX "/apps/totem" + +typedef struct Totem Totem; + +void totem_action_exit (Totem *totem); +void totem_action_play (Totem *totem); +void totem_action_stop (Totem *totem); +void totem_action_play_pause (Totem *totem); +void totem_action_pause (Totem *totem); +void totem_action_fullscreen_toggle (Totem *totem); +void totem_action_fullscreen (Totem *totem, gboolean state); +void totem_action_next (Totem *totem); +void totem_action_previous (Totem *totem); +void totem_action_seek_relative (Totem *totem, int off_sec); +void totem_action_volume_relative (Totem *totem, int off_pct); +gboolean totem_action_set_mrl (Totem *totem, + const char *mrl); +void totem_action_set_mrl_and_play (Totem *totem, + const char *mrl); + +gboolean totem_action_set_mrl_with_warning (Totem *totem, + const char *mrl, + gboolean warn); + +void totem_action_play_media (Totem *totem, + MediaType type); + +void totem_action_toggle_aspect_ratio (Totem *totem); +void totem_action_set_aspect_ratio (Totem *totem, int ratio); +int totem_action_get_aspect_ratio (Totem *totem); +void totem_action_toggle_controls (Totem *totem); + +void totem_action_set_scale_ratio (Totem *totem, gfloat ratio); +void totem_action_error (const char *title, + const char *reason, + Totem *totem); +void totem_action_play_media_device (Totem *totem, + const char *device); + +#endif /* __TOTEM_H__ */ diff --git a/trunk/src/totem_mozilla_scripting.idl b/trunk/src/totem_mozilla_scripting.idl new file mode 100644 index 000000000..088f89a7c --- /dev/null +++ b/trunk/src/totem_mozilla_scripting.idl @@ -0,0 +1,29 @@ +/* Totem Mozilla plugin + * + * Copyright (C) <2004> Bastien Nocera <hadess@hadess.net> + * Copyright (C) <2002> David A. Schleef <ds@schleef.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "nsISupports.idl" + +[scriptable, uuid(862af69e-16e2-4016-ad18-77fa88a1e488)] +interface totemMozillaScript : nsISupports { + void Play (); + void Rewind (); + void Stop (); +}; diff --git a/trunk/src/update-from-egg.sh b/trunk/src/update-from-egg.sh new file mode 100755 index 000000000..9be68a9b4 --- /dev/null +++ b/trunk/src/update-from-egg.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +function die() { + echo $* + exit 1 +} + +if test -z "$EGGDIR"; then + echo "Must set EGGDIR" + exit 1 +fi + +if test -z "$EGGFILES"; then + echo "Must set EGGFILES" + exit 1 +fi + +for FILE in $EGGFILES; do + if cmp -s $EGGDIR/$FILE $FILE; then + echo "File $FILE is unchanged" + else + cp $EGGDIR/$FILE $FILE || die "Could not move $EGGDIR/$FILE to $FILE" + echo "Updated $FILE" + fi +done diff --git a/trunk/src/vanity.c b/trunk/src/vanity.c new file mode 100644 index 000000000..52b86f7f8 --- /dev/null +++ b/trunk/src/vanity.c @@ -0,0 +1,741 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001,2002,2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include <config.h> +#include <X11/Xlib.h> +#ifndef HAVE_GTK_ONLY +#include <gnome.h> +#else +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <stdlib.h> +#endif /* !HAVE_GTK_ONLY */ + +#include <glib/gi18n.h> +#include <glade/glade.h> +#include <libgnomevfs/gnome-vfs.h> +#include <gconf/gconf-client.h> +#include <string.h> + +#include "bacon-video-widget.h" +#include "totem-screenshot.h" + +#include "debug.h" + +typedef struct Vanity Vanity; + +struct Vanity { + /* Main window */ + GladeXML *xml; + GtkWidget *win; + BaconVideoWidget *bvw; + + /* Prefs */ + GtkWidget *prefs; + GConfClient *gc; + + /* Widgets */ + GtkAboutDialog *about; + + gboolean debug; +}; + +static const GtkTargetEntry source_table[] = { + { "text/uri-list", 0, 0 }, +}; + +static void vanity_action_exit (Vanity *vanity); + +static struct poptOption options[] = { + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, NULL, 0, N_("Backend options"), NULL}, + {"debug", '\0', POPT_ARG_NONE, NULL, 0, N_("Debug mode on"), NULL}, + {NULL, '\0', 0, NULL, 0} /* end the list */ +}; + +static void +long_action (void) +{ + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static void +vanity_action_error (char *title, char *reason, Vanity *vanity) +{ + GtkWidget *parent, *error_dialog; + + if (vanity == NULL) + parent = NULL; + else + parent = vanity->win; + + error_dialog = + gtk_message_dialog_new (GTK_WINDOW (parent), + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + title); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), reason); + + gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5); + gtk_dialog_set_default_response (GTK_DIALOG (error_dialog), + GTK_RESPONSE_OK); + g_signal_connect (G_OBJECT (error_dialog), "destroy", G_CALLBACK + (gtk_widget_destroy), error_dialog); + g_signal_connect (G_OBJECT (error_dialog), "response", G_CALLBACK + (gtk_widget_destroy), error_dialog); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + + gtk_widget_show (error_dialog); +} + +static void +vanity_action_error_and_exit (char *msg, Vanity *vanity) +{ + GtkWidget *error_dialog; + + error_dialog = + gtk_message_dialog_new (NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", msg); + gtk_dialog_set_default_response (GTK_DIALOG (error_dialog), + GTK_RESPONSE_OK); + gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE); + gtk_dialog_run (GTK_DIALOG (error_dialog)); + + vanity_action_exit (vanity); +} + +static void +vanity_action_exit (Vanity *vanity) +{ + if (gtk_main_level () > 0) + gtk_main_quit (); + + if (vanity == NULL) + exit (0); + + if (vanity->win) + gtk_widget_hide (vanity->win); + + if (vanity->bvw) + gtk_widget_destroy (GTK_WIDGET (vanity->bvw)); + + exit (0); +} + +static gboolean +main_window_destroy_cb (GtkWidget *widget, GdkEvent *event, Vanity *vanity) +{ + vanity_action_exit (vanity); + + return FALSE; +} + +static void +vanity_action_set_scale_ratio (Vanity *vanity, gfloat ratio) +{ + bacon_video_widget_set_scale_ratio (vanity->bvw, ratio); +} + +static void +drag_video_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + gpointer callback_data) +{ +#if 0 + Vanity *vanity = (Vanity *) callback_data; + //FIXME the trick would be to create a file like gnome-panel-screenshot does + + char *text; + int len; + + g_assert (selection_data != NULL); + + if (vanity->mrl == NULL) + return; + + if (vanity->mrl[0] == '/') + text = gnome_vfs_get_uri_from_local_path (vanity->mrl); + else + text = g_strdup (vanity->mrl); + + g_return_if_fail (text != NULL); + + len = strlen (text); + + gtk_selection_data_set (selection_data, + selection_data->target, + 8, (guchar *) text, len); + + g_free (text); +#endif +} + +static void +on_zoom_1_2_activate (GtkButton *button, Vanity *vanity) +{ + vanity_action_set_scale_ratio (vanity, 0.5); +} + +static void +on_zoom_1_1_activate (GtkButton *button, Vanity *vanity) +{ + vanity_action_set_scale_ratio (vanity, 1); +} + +static void +on_zoom_2_1_activate (GtkButton *button, Vanity *vanity) +{ + vanity_action_set_scale_ratio (vanity, 2); +} + + +static void +on_quit1_activate (GtkButton *button, Vanity *vanity) +{ + vanity_action_exit (vanity); +} + +static void +on_about1_activate (GtkButton *button, Vanity *vanity) +{ + GdkPixbuf *pixbuf = NULL; + const char *authors[] = + { + "Bastien Nocera <hadess@hadess.net>", + NULL + }; + const char *artists[] = { NULL }; + const char *documenters[] = { NULL }; + char *backend_version, *description; + char *filename; + + if (vanity->about != NULL) + { + gtk_window_present (GTK_WINDOW (vanity->about)); + return; + } + + filename = g_build_filename (DATADIR, + "totem", "vanity.png", NULL); + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + g_free (filename); + + backend_version = bacon_video_widget_get_backend_name (vanity->bvw); + description = g_strdup_printf (_("Webcam utility using %s"), + backend_version); + + vanity->about = g_object_new (GTK_TYPE_ABOUT_DIALOG, + "name", _("Vanity"), + "version", VERSION, + "copyright", _("Copyright \xc2\xa9 2002-2005 Bastien Nocera"), + "comments", description, + "authors", authors, + "documenters", documenters, + "artists", artists, + "translator-credits", _("translator-credits"), + "logo", pixbuf, + NULL); + + g_free (backend_version); + g_free (description); + + if (pixbuf != NULL) + gdk_pixbuf_unref (pixbuf); + + g_object_add_weak_pointer (G_OBJECT (vanity->about), + (gpointer *)&vanity->about); + gtk_window_set_transient_for (GTK_WINDOW (vanity->about), + GTK_WINDOW (vanity->win)); + g_signal_connect (G_OBJECT (vanity->about), "response", + G_CALLBACK (gtk_widget_destroy), NULL); + + gtk_widget_show(GTK_WIDGET (vanity->about)); +} + +static void +on_save1_activate (GtkButton *button, Vanity *vanity) +{ + GdkPixbuf *pixbuf; + GtkWidget *dialog; + char *filename; + GError *err = NULL; + + if (bacon_video_widget_can_get_frames (vanity->bvw, &err) == FALSE) + { + if (err == NULL) + return; + + vanity_action_error (_("Totem could not get a screenshot of that film."), err->message, vanity); + g_error_free (err); + return; + } + + pixbuf = bacon_video_widget_get_current_frame (vanity->bvw); + if (pixbuf == NULL) + { + vanity_action_error (_("Totem could not get a screenshot of that film."), _("This is not supposed to happen; please file a bug report."), vanity); + return; + } + + filename = g_build_filename (DATADIR, + "totem", "screenshot.glade", NULL); + + dialog = totem_screenshot_new (filename, pixbuf); + g_free (filename); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + gdk_pixbuf_unref (pixbuf); +} + +static void +on_preferences1_activate (GtkButton *button, Vanity *vanity) +{ + gtk_widget_show (vanity->prefs); +} + +static gboolean +vanity_action_handle_key (Vanity *vanity, GdkEventKey *event) +{ + gboolean retval = TRUE; + + /* Alphabetical */ + switch (event->keyval) { +#if 0 + case GDK_A: + case GDK_a: + vanity_action_toggle_aspect_ratio (vanity); + if (vanity->action == 0) + vanity->action++; + else + vanity->action = 0; + break; + case XF86XK_AudioPrev: + case GDK_B: + case GDK_b: + vanity_action_previous (vanity); + break; + case GDK_C: + case GDK_c: + bacon_video_widget_dvd_event (vanity->bvw, BVW_DVD_CHAPTER_MENU); + if (vanity->action == 1) + vanity->action++; + else + vanity->action = 0; + break; + case GDK_f: + case GDK_F: + if (event->time - vanity->keypress_time + >= KEYBOARD_HYSTERISIS_TIMEOUT) + vanity_action_fullscreen_toggle (vanity); + + vanity->keypress_time = event->time; + + break; + case GDK_h: + case GDK_H: + { + GtkCheckMenuItem *item; + gboolean value; + + item = GTK_CHECK_MENU_ITEM (glade_xml_get_widget + (vanity->xml, "show_controls1")); + value = gtk_check_menu_item_get_active (item); + gtk_check_menu_item_set_active (item, !value); + } + break; + case GDK_i: + case GDK_I: + { + GtkCheckMenuItem *item; + gboolean value; + + item = GTK_CHECK_MENU_ITEM (glade_xml_get_widget + (vanity->xml, "deinterlace1")); + value = gtk_check_menu_item_get_active (item); + gtk_check_menu_item_set_active (item, !value); + } + + if (vanity->action == 3) + vanity->action++; + else + vanity->action = 0; + break; + case GDK_M: + case GDK_m: + bacon_video_widget_dvd_event (vanity->bvw, BVW_DVD_ROOT_MENU); + break; + case XF86XK_AudioNext: + case GDK_N: + case GDK_n: + vanity_action_next (vanity); + if (vanity->action == 5) + vanity_action_set_mrl_and_play (vanity, "v4l://"); + vanity->action = 0; + break; + case GDK_O: + case GDK_o: + vanity_action_fullscreen (vanity, FALSE); + on_open1_activate (NULL, (gpointer) vanity); + if (vanity->action == 4) + vanity->action++; + else + vanity->action = 0; + break; + case XF86XK_AudioPlay: + case XF86XK_AudioPause: + case GDK_p: + case GDK_P: + vanity_action_play_pause (vanity); + break; + case GDK_q: + case GDK_Q: + vanity_action_exit (vanity); + break; + case GDK_s: + case GDK_S: + on_skip_to1_activate (NULL, vanity); + break; + case GDK_T: + if (vanity->action == 2) + vanity->action++; + else + vanity->action = 0; + break; + case GDK_Escape: + vanity_action_fullscreen (vanity, FALSE); + break; + case GDK_Left: + vanity_action_seek_relative (vanity, SEEK_BACKWARD_OFFSET); + break; + case GDK_Right: + vanity_action_seek_relative (vanity, SEEK_FORWARD_OFFSET); + break; + case GDK_Up: + vanity_action_volume_relative (vanity, VOLUME_UP_OFFSET); + break; + case GDK_Down: + vanity_action_volume_relative (vanity, VOLUME_DOWN_OFFSET); + break; + case GDK_0: + case GDK_onehalf: + vanity_action_set_scale_ratio (vanity, 0.5); + break; + case GDK_1: + vanity_action_set_scale_ratio (vanity, 1); + break; + case GDK_2: + vanity_action_set_scale_ratio (vanity, 2); + break; +#endif + default: + retval = FALSE; + } + + return retval; +} + +static int +on_window_key_press_event (GtkWidget *win, GdkEventKey *event, Vanity *vanity) +{ + /* If we have modifiers, and either Ctrl, Mod1 (Alt), or any + * of Mod3 to Mod5 (Mod2 is num-lock...) are pressed, we + * let Gtk+ handle the key */ + if (event->state != 0 + && ((event->state & GDK_CONTROL_MASK) + || (event->state & GDK_MOD1_MASK) + || (event->state & GDK_MOD3_MASK) + || (event->state & GDK_MOD4_MASK) + || (event->state & GDK_MOD5_MASK))) + return FALSE; + + return vanity_action_handle_key (vanity, event); +} + +static void +vanity_callback_connect (Vanity *vanity) +{ + GtkWidget *item; + + /* Menu items */ + item = glade_xml_get_widget (vanity->xml, "save1"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_save1_activate), vanity); + item = glade_xml_get_widget (vanity->xml, "zoom_12"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_zoom_1_2_activate), vanity); + item = glade_xml_get_widget (vanity->xml, "zoom_11"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_zoom_1_1_activate), vanity); + item = glade_xml_get_widget (vanity->xml, "zoom_21"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_zoom_2_1_activate), vanity); + item = glade_xml_get_widget (vanity->xml, "quit1"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_quit1_activate), vanity); +#ifndef HAVE_GTK_ONLY + item = glade_xml_get_widget (vanity->xml, "about1"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_about1_activate), vanity); +#else + item = glade_xml_get_widget (vanity->xml, "help1"); + gtk_widget_hide (item); +#endif /* !HAVE_GTK_ONLY */ + + item = glade_xml_get_widget (vanity->xml, "preferences1"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_preferences1_activate), vanity); + + /* The toolbar */ +#if 0 + item = glade_xml_get_widget (vanity->xml, "save_button"); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (on_save1_activate), vanity); +#endif + /* Exit */ + g_signal_connect (G_OBJECT (vanity->win), "delete-event", + G_CALLBACK (main_window_destroy_cb), vanity); + g_signal_connect (G_OBJECT (vanity->win), "destroy", + G_CALLBACK (main_window_destroy_cb), vanity); + + /* Connect the keys */ + gtk_widget_add_events (vanity->win, GDK_KEY_PRESS_MASK); + g_signal_connect (G_OBJECT(vanity->win), "key_press_event", + G_CALLBACK (on_window_key_press_event), vanity); +} + +static void +video_widget_create (Vanity *vanity) +{ + GError *err = NULL; + GtkWidget *container; + + vanity->bvw = BACON_VIDEO_WIDGET + (bacon_video_widget_new (-1, -1, FALSE, &err)); + + if (vanity->bvw == NULL) + { + char *msg; + + msg = g_strdup_printf (_("Vanity could not startup:\n%s"), + err != NULL ? err->message : _("No reason")); + + if (err != NULL) + g_error_free (err); + + gtk_widget_hide (vanity->win); + vanity_action_error_and_exit (msg, vanity); + } + + container = glade_xml_get_widget (vanity->xml, "frame1"); + gtk_container_add (GTK_CONTAINER (container), + GTK_WIDGET (vanity->bvw)); + + /* Events for the widget video window as well */ + gtk_widget_add_events (GTK_WIDGET (vanity->bvw), GDK_KEY_PRESS_MASK); + g_signal_connect (G_OBJECT(vanity->bvw), "key_press_event", + G_CALLBACK (on_window_key_press_event), vanity); + + g_signal_connect (G_OBJECT (vanity->bvw), "drag_data_get", + G_CALLBACK (drag_video_cb), vanity); + gtk_drag_source_set (GTK_WIDGET (vanity->bvw), + GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + source_table, G_N_ELEMENTS (source_table), + GDK_ACTION_LINK); + + g_object_add_weak_pointer (G_OBJECT (vanity->bvw), + (void**)&(vanity->bvw)); + + gtk_widget_show (GTK_WIDGET (vanity->bvw)); + + if (vanity->debug == FALSE) + { + bacon_video_widget_open (vanity->bvw, "v4l:/", &err); + + if (err != NULL) + { + char *msg; + + msg = g_strdup_printf (_("Vanity could not contact the webcam.\nReason: %s"), err->message); + g_error_free (err); + vanity_action_error_and_exit (msg, vanity); + } + + bacon_video_widget_play (vanity->bvw, &err); + } else { + bacon_video_widget_set_logo (vanity->bvw, LOGO_PATH); + bacon_video_widget_set_logo_mode (vanity->bvw, TRUE); + g_message ("%s", LOGO_PATH); + } + + if (err != NULL) + { + char *msg; + + msg = g_strdup_printf (_("Vanity could not play video from the webcam.\nReason: %s"), err->message); + g_error_free (err); + gtk_widget_hide (vanity->win); + vanity_action_error_and_exit (msg, vanity); + } + + gtk_widget_set_size_request (container, -1, -1); +} + +static void +process_command_line (Vanity *vanity, int argc, char **argv) +{ + int i; + + if (argc == 1) + return; + + for (i = 0; i < argc; i++) + { + if (strcmp (argv[i], "--debug") == 0) + vanity->debug = TRUE; + } +} + +int +main (int argc, char **argv) +{ + Vanity *vanity; + char *filename; + GConfClient *gc; + GError *err = NULL; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_set_application_name (_("Vanity Webcam Utility")); + + if (XInitThreads () == 0) + { + gtk_init (&argc, &argv); + vanity_action_error_and_exit (_("Could not initialize the " + "thread-safe libraries.\n" + "Verify your system installation. " + "Vanity will now exit."), NULL); + } + + g_thread_init (NULL); + gdk_threads_init (); + + gtk_init (&argc, &argv); +#ifndef HAVE_GTK_ONLY + gnome_program_init ("vanity", VERSION, + LIBGNOMEUI_MODULE, + argc, argv, + GNOME_PARAM_APP_DATADIR, DATADIR, + GNOME_PARAM_POPT_TABLE, options, + GNOME_PARAM_NONE); + options[0].arg = bacon_video_widget_get_popt_table (); +#endif /* !HAVE_GTK_ONLY */ + + glade_init (); + gnome_vfs_init (); + if ((gc = gconf_client_get_default ()) == NULL) + { + char *str; + + str = g_strdup_printf (_("Vanity could not initialize the \n" + "configuration engine:\n%s"), + err->message); + vanity_action_error_and_exit (str, NULL); + g_error_free (err); + g_free (str); + } + +#ifndef HAVE_GTK_ONLY + gnome_authentication_manager_init (); +#endif /* !HAVE_GTK_ONLY */ + +#ifdef TOTEM_RUN_IN_SOURCE_TREE + if (g_file_test ("../data/vanity.glade", G_FILE_TEST_EXISTS) != FALSE) + filename = g_strdup ("../data/vanity.glade"); + else +#endif + filename = g_build_filename (DATADIR, + "totem", "vanity.glade", NULL); + + if (g_file_test (filename, G_FILE_TEST_EXISTS) == FALSE) + { + vanity_action_error_and_exit (_("Couldn't load the main " + "interface (vanity.glade).\n" + "Make sure that Vanity" + " is properly installed."), NULL); + } + + vanity = g_new0 (Vanity, 1); + + process_command_line (vanity, argc, argv); + + /* Main window */ + vanity->xml = glade_xml_new (filename, NULL, NULL); + if (vanity->xml == NULL) + { + g_free (filename); + vanity_action_error_and_exit (_("Couldn't load the main " + "interface (vanity.glade).\n" + "Make sure that Vanity" + " is properly installed."), NULL); + } + g_free (filename); + + vanity->win = glade_xml_get_widget (vanity->xml, "window1"); + filename = g_build_filename (DATADIR, "totem", "vanity.png", NULL); + gtk_window_set_default_icon_from_file (filename, NULL); + g_free (filename); + + /* The rest of the widgets */ + vanity->prefs = glade_xml_get_widget (vanity->xml, "dialog1"); + vanity_callback_connect (vanity); + + /* Show ! gtk_main_iteration trickery to show all the widgets + * we have so far */ + gtk_widget_show_all (vanity->win); + long_action (); + + /* Show ! (again) the video widget this time. */ + video_widget_create (vanity); + + /* The prefs after the video widget is connected */ +// vanity_setup_preferences (vanity); + + gtk_main (); + + return 0; +} + diff --git a/trunk/src/video-dev.c b/trunk/src/video-dev.c new file mode 100644 index 000000000..e6d33bd84 --- /dev/null +++ b/trunk/src/video-dev.c @@ -0,0 +1,221 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + * + * video-dev.c: detection of video devices + * + * Copyright (C) 2003 Bastien Nocera + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Bastien Nocera <hadess@hadess.net> + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <math.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "video-dev.h" + +#ifdef __linux__ + +static char ** +read_lines (char *filename) +{ + char *contents; + gsize len; + char *p, *n; + GPtrArray *array; + + if (g_file_get_contents (filename, + &contents, + &len, NULL)) { + + array = g_ptr_array_new (); + + p = contents; + while ((n = memchr (p, '\n', len - (p - contents))) != NULL) { + *n = 0; + g_ptr_array_add (array, g_strdup (p)); + p = n + 1; + } + if ((gsize)(p - contents) < len) { + g_ptr_array_add (array, g_strndup (p, len - (p - contents))); + } + + g_ptr_array_add (array, NULL); + + g_free (contents); + return (char **)g_ptr_array_free (array, FALSE); + } + return NULL; +} + +static char* +linux_get_device_path (const char *name) +{ + char *filename; + + filename = g_build_filename ("/dev", name, NULL); + if (g_file_test (filename, G_FILE_TEST_EXISTS) != FALSE) { + return filename; + } + + g_free (filename); + filename = g_build_filename ("/dev/v4l", name, NULL); + if (g_file_test (filename, G_FILE_TEST_EXISTS) != FALSE) { + return filename; + } + + g_free (filename); + return filename; +} + +static VideoDev* +linux_add_video_dev (const char *name) +{ + VideoDev *dev; + char *proc; + char **lines, *tmp, *filename; + + filename = linux_get_device_path (name); + if (filename == NULL) { + return NULL; + } + + if (g_file_test ("/proc/video/dev", G_FILE_TEST_IS_DIR) != FALSE) { + proc = g_build_filename ("/proc/video/dev", name, NULL); + lines = read_lines (proc); + g_free (proc); + + if (lines == NULL) { + return NULL; + } + + if (g_str_has_prefix (lines[0], "name") == FALSE) { + g_strfreev (lines); + return NULL; + } + + tmp = strstr (lines[0], ":"); + if (tmp == NULL || tmp + 1 == NULL || tmp + 2 == NULL) { + g_strfreev (lines); + return NULL; + } + tmp = tmp + 2; + } else { + proc = g_build_filename ("/sys/class/video4linux", + name, "model", NULL); + lines = read_lines (proc); + g_free (proc); + + if (lines == NULL) { + proc = g_build_filename ("/sys/class/video4linux", + name, "name", NULL); + lines = read_lines (proc); + g_free (proc); + + if (lines == NULL) { + return NULL; + } + } + + tmp = lines[0]; + } + + dev = g_new0 (VideoDev, 1); + dev->display_name = g_strdup (tmp); + dev->device = filename; + + g_strfreev (lines); + + return dev; +} + +static GList * +linux_scan (void) +{ + GList *devs = NULL; + GDir *dir; + const char *name; + VideoDev *dev; + + if (g_file_test ("/proc/video/dev", G_FILE_TEST_IS_DIR) != FALSE) { + dir = g_dir_open ("/proc/video/dev", 0, NULL); + } else if (g_file_test ("/sys/class/video4linux", G_FILE_TEST_IS_DIR) != FALSE) { + dir = g_dir_open ("/sys/class/video4linux", 0, NULL); + } else { + return NULL; + } + + name = g_dir_read_name (dir); + while (name != NULL) { + dev = linux_add_video_dev (name); + if (dev != NULL) { + devs = g_list_prepend (devs, dev); + } + name = g_dir_read_name (dir); + } + g_dir_close (dir); + + if (devs != NULL) { + devs = g_list_reverse (devs); + } + + return devs; +} +#endif /* __linux__ */ + +GList * +scan_for_video_devices (void) +{ + GList *devs = NULL; + +#ifdef __linux__ + devs = linux_scan (); +#endif + + return devs; +} + +void +video_dev_free (VideoDev *dev) +{ + g_return_if_fail (dev != NULL); + + g_free (dev->display_name); + g_free (dev->device); + g_free (dev); +} + diff --git a/trunk/src/video-dev.h b/trunk/src/video-dev.h new file mode 100644 index 000000000..e193c4c90 --- /dev/null +++ b/trunk/src/video-dev.h @@ -0,0 +1,48 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + * + * video-dev.h: detection of video devices + * + * Copyright (C) 2003 Bastien Nocera + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Bastien Nocera <hadess@hadess.net> + * + * The Totem project hereby grant permission for non-gpl compatible GStreamer + * plugins to be used and distributed together with GStreamer and Totem. This + * permission are above and beyond the permissions granted by the GPL license + * Totem is covered by. + * + * Monday 7th February 2005: Christian Schaller: Add excemption clause. + * See license_change file for details. + * + */ + +#ifndef VIDEO_DEV_H +#define VIDEO_DEV_H + +#include <glib.h> + +typedef struct { + char *display_name; + char *device; +} VideoDev; + +/* Returns a list of VideoDev structs */ +GList *scan_for_video_devices (void); +void video_dev_free (VideoDev *dev); + +#endif |