summaryrefslogtreecommitdiff
path: root/trunk/src
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src')
-rw-r--r--trunk/src/.cvsignore21
-rw-r--r--trunk/src/Makefile.am404
-rw-r--r--trunk/src/backend/.cvsignore4
-rw-r--r--trunk/src/backend/Makefile.am67
-rw-r--r--trunk/src/backend/bacon-resize.c157
-rw-r--r--trunk/src/backend/bacon-resize.h25
-rw-r--r--trunk/src/backend/bacon-video-widget-common.c72
-rw-r--r--trunk/src/backend/bacon-video-widget-common.h50
-rw-r--r--trunk/src/backend/bacon-video-widget-gst-0.10.c5150
-rw-r--r--trunk/src/backend/bacon-video-widget-xine.c4058
-rw-r--r--trunk/src/backend/bacon-video-widget.h350
-rw-r--r--trunk/src/backend/baconvideowidget-marshal.list3
-rw-r--r--trunk/src/backend/bvw-test.c93
-rw-r--r--trunk/src/backend/debug.h38
-rw-r--r--trunk/src/backend/gstscreenshot.c199
-rw-r--r--trunk/src/backend/gstscreenshot.h32
-rw-r--r--trunk/src/backend/video-utils.c374
-rw-r--r--trunk/src/backend/video-utils.h25
-rw-r--r--trunk/src/bacon-message-connection.c396
-rw-r--r--trunk/src/bacon-message-connection.h43
-rw-r--r--trunk/src/bacon-v4l-selection.c347
-rw-r--r--trunk/src/bacon-v4l-selection.h65
-rw-r--r--trunk/src/bacon-video-widget-properties.c328
-rw-r--r--trunk/src/bacon-video-widget-properties.h63
-rw-r--r--trunk/src/bacon-volume.c846
-rw-r--r--trunk/src/bacon-volume.h57
-rw-r--r--trunk/src/baconvideowidget-marshal.list2
-rw-r--r--trunk/src/disc-test.c111
-rw-r--r--trunk/src/ev-sidebar.c451
-rw-r--r--trunk/src/ev-sidebar.h71
-rw-r--r--trunk/src/list_v4l.c35
-rw-r--r--trunk/src/plparse/.cvsignore7
-rw-r--r--trunk/src/plparse/Makefile.am164
-rw-r--r--trunk/src/plparse/plparser.symbols18
-rw-r--r--trunk/src/plparse/test-parser.c316
-rw-r--r--trunk/src/plparse/totem-disc.c925
-rw-r--r--trunk/src/plparse/totem-disc.h51
-rw-r--r--trunk/src/plparse/totem-pl-parser-features.h.in42
-rw-r--r--trunk/src/plparse/totem-pl-parser-lines.c415
-rw-r--r--trunk/src/plparse/totem-pl-parser-lines.h59
-rw-r--r--trunk/src/plparse/totem-pl-parser-media.c272
-rw-r--r--trunk/src/plparse/totem-pl-parser-media.h53
-rw-r--r--trunk/src/plparse/totem-pl-parser-mini.h38
-rw-r--r--trunk/src/plparse/totem-pl-parser-misc.c137
-rw-r--r--trunk/src/plparse/totem-pl-parser-misc.h47
-rw-r--r--trunk/src/plparse/totem-pl-parser-pls.c311
-rw-r--r--trunk/src/plparse/totem-pl-parser-pls.h52
-rw-r--r--trunk/src/plparse/totem-pl-parser-private.h95
-rw-r--r--trunk/src/plparse/totem-pl-parser-qt.c174
-rw-r--r--trunk/src/plparse/totem-pl-parser-qt.h45
-rw-r--r--trunk/src/plparse/totem-pl-parser-smil.c186
-rw-r--r--trunk/src/plparse/totem-pl-parser-smil.h43
-rw-r--r--trunk/src/plparse/totem-pl-parser-wm.c346
-rw-r--r--trunk/src/plparse/totem-pl-parser-wm.h50
-rw-r--r--trunk/src/plparse/totem-pl-parser-xspf.c261
-rw-r--r--trunk/src/plparse/totem-pl-parser-xspf.h48
-rw-r--r--trunk/src/plparse/totem-pl-parser.c1206
-rw-r--r--trunk/src/plparse/totem-pl-parser.h123
-rw-r--r--trunk/src/plparse/totemplparser-marshal.list1
-rw-r--r--trunk/src/test-properties-page.c106
-rw-r--r--trunk/src/test.html51
-rw-r--r--trunk/src/totem-gromit.c191
-rw-r--r--trunk/src/totem-gromit.h33
-rw-r--r--trunk/src/totem-interface.c256
-rw-r--r--trunk/src/totem-interface.h54
-rw-r--r--trunk/src/totem-marshal.list1
-rw-r--r--trunk/src/totem-menu.c1388
-rw-r--r--trunk/src/totem-menu.h44
-rw-r--r--trunk/src/totem-missing-plugins.c267
-rw-r--r--trunk/src/totem-missing-plugins.h34
-rw-r--r--trunk/src/totem-options.c230
-rw-r--r--trunk/src/totem-options.h67
-rw-r--r--trunk/src/totem-playlist.c2282
-rw-r--r--trunk/src/totem-playlist.h124
-rw-r--r--trunk/src/totem-preferences.c732
-rw-r--r--trunk/src/totem-preferences.h42
-rw-r--r--trunk/src/totem-private.h206
-rw-r--r--trunk/src/totem-properties-main.c157
-rw-r--r--trunk/src/totem-properties-view.c199
-rw-r--r--trunk/src/totem-properties-view.h59
-rw-r--r--trunk/src/totem-remote.c364
-rw-r--r--trunk/src/totem-remote.h96
-rw-r--r--trunk/src/totem-screenshot.c424
-rw-r--r--trunk/src/totem-screenshot.h55
-rw-r--r--trunk/src/totem-scrsaver.c455
-rw-r--r--trunk/src/totem-scrsaver.h51
-rw-r--r--trunk/src/totem-session.c172
-rw-r--r--trunk/src/totem-session.h35
-rw-r--r--trunk/src/totem-sidebar.c149
-rw-r--r--trunk/src/totem-sidebar.h38
-rw-r--r--trunk/src/totem-skipto.c196
-rw-r--r--trunk/src/totem-skipto.h58
-rw-r--r--trunk/src/totem-statusbar.c674
-rw-r--r--trunk/src/totem-statusbar.h105
-rw-r--r--trunk/src/totem-subtitle-encoding.c586
-rw-r--r--trunk/src/totem-subtitle-encoding.h12
-rw-r--r--trunk/src/totem-time-label.c106
-rw-r--r--trunk/src/totem-time-label.h37
-rw-r--r--trunk/src/totem-uri.c324
-rw-r--r--trunk/src/totem-uri.h45
-rw-r--r--trunk/src/totem-video-indexer.c211
-rw-r--r--trunk/src/totem-video-thumbnailer.c509
-rw-r--r--trunk/src/totem.c3566
-rw-r--r--trunk/src/totem.h74
-rw-r--r--trunk/src/totem_mozilla_scripting.idl29
-rwxr-xr-xtrunk/src/update-from-egg.sh25
-rw-r--r--trunk/src/vanity.c741
-rw-r--r--trunk/src/video-dev.c221
-rw-r--r--trunk/src/video-dev.h48
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),
+ &GTK_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,
+ &current_position,
+ &current_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(&current_time, NULL); \
+ dtime = -(current_time.tv_sec + (current_time.tv_usec / 1000000.0)); \
+ function; \
+ gettimeofday(&current_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, &current, path);
+ gtk_tree_path_free (path);
+
+ if (direction_up == FALSE)
+ {
+ pos--;
+ gtk_list_store_move_before (store, &current, position);
+ } else {
+ gtk_list_store_move_after (store, &current, 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