/* Totem Plugin Viewer * * Copyright © 2004-2006 Bastien Nocera * Copyright © 2002 David A. Schleef * Copyright © 2006, 2009 Christian Persch * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bacon-video-widget.h" #include "totem-interface.h" #include "totem-time-label.h" #include "totem-glow-button.h" #include "video-utils.h" #include "totem-plugin-viewer-resources.h" #include "totem-plugin-viewer-constants.h" #include "totem-plugin-viewer-options.h" GtkWidget *totem_volume_create (void); GtkWidget *totem_pp_create (void); /* Private function in totem-pl-parser, not for use * by anyone but us */ char * totem_pl_parser_resolve_uri (GFile *base_gfile, const char *relative_uri); #define VOLUME_DOWN_OFFSET (-0.08) #define VOLUME_UP_OFFSET (0.08) #define MINIMUM_VIDEO_SIZE 150 #define IMAGE_MISSING "image-missing" typedef enum { TOTEM_PLUGIN_TYPE_GMP, TOTEM_PLUGIN_TYPE_NARROWSPACE, TOTEM_PLUGIN_TYPE_MULLY, TOTEM_PLUGIN_TYPE_CONE, TOTEM_PLUGIN_TYPE_VEGAS, TOTEM_PLUGIN_TYPE_LAST } TotemPluginType; #define TOTEM_TYPE_EMBEDDED (totem_embedded_get_type ()) #define TOTEM_EMBEDDED(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TOTEM_TYPE_EMBEDDED, TotemEmbedded)) #define TOTEM_EMBEDDED_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TOTEM_TYPE_EMBEDDED, TotemEmbeddedClass)) #define TOTEM_IS_EMBEDDED(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TOTEM_TYPE_EMBEDDED)) #define TOTEM_IS_EMBEDDED_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TOTEM_TYPE_EMBEDDED)) #define TOTEM_EMBEDDED_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), TOTEM_TYPE_EMBEDDED, TotemEmbeddedClass)) typedef struct { char *uri; char *subtitle; char *title; int duration; int starttime; } TotemPlItem; typedef struct { GtkApplicationClass parent; } TotemEmbeddedClass; typedef struct _TotemEmbedded { GtkApplication parent; /* D-Bus stuff */ guint name_id; GDBusConnection *conn; GDBusNodeInfo *introspection_data; GtkWidget *window; GtkBuilder *menuxml, *xml; GtkWidget *pp_button; // GtkWidget *pp_fs_button; int width, height; char *user_agent; const char *mimetype; char *referrer_uri; char *base_uri; char *current_uri; char *current_subtitle_uri; char *href_uri; char *target; char *stream_uri; gint64 size; /* the size of the streamed file for fd://0 */ BaconVideoWidget *bvw; TotemStates state; GdkCursor *cursor; guint inhibit_id; /* Playlist, a GList of TotemPlItem */ GList *playlist, *current; guint parser_id; int num_items; gboolean remove_copy; /* Open menu item */ GAppInfo *app; GtkWidget *menu_item; /* Seek bits */ GtkAdjustment *seekadj; GtkWidget *seek; /* Volume */ GtkWidget *volume; /* Fullscreen */ gboolean fullscreen; GtkWidget * fs_window; /* Error */ GError *error; guint type : 3; /* TotemPluginType */ guint is_browser_stream : 1; guint is_playlist : 1; guint controller_hidden : 1; guint hidden : 1; guint repeat : 1; guint seeking : 1; guint autostart : 1; guint audioonly : 1; } TotemEmbedded; GType totem_embedded_get_type (void); #define TOTEM_EMBEDDED_ERROR_QUARK (g_quark_from_static_string ("TotemEmbeddedErrorQuark")) enum { TOTEM_EMBEDDED_UNKNOWN_PLUGIN_TYPE, TOTEM_EMBEDDED_SETWINDOW_UNSUPPORTED_CONTROLS, TOTEM_EMBEDDED_SETWINDOW_HAVE_WINDOW, TOTEM_EMBEDDED_SETWINDOW_INVALID_XID, TOTEM_EMBEDDED_NO_URI, TOTEM_EMBEDDED_OPEN_FAILED, TOTEM_EMBEDDED_UNKNOWN_COMMAND }; G_DEFINE_TYPE (TotemEmbedded, totem_embedded, GTK_TYPE_APPLICATION); static void totem_embedded_init (TotemEmbedded *emb) { } static gboolean totem_embedded_push_parser (gpointer data); static gboolean totem_embedded_play (TotemEmbedded *embedded, GError **error); static void totem_embedded_set_logo_by_name (TotemEmbedded *embedded, const char *name); static void totem_embedded_update_menu (TotemEmbedded *emb); static void on_open1_activate (GtkButton *button, TotemEmbedded *emb); static void totem_embedded_toggle_fullscreen (TotemEmbedded *emb); void on_preferences1_activate (GtkButton *button, TotemEmbedded *emb); void on_copy_location1_activate (GtkButton *button, TotemEmbedded *emb); void on_fullscreen1_activate (GtkMenuItem *menuitem, TotemEmbedded *emb); static void update_fill (TotemEmbedded *emb, gdouble level); enum { BUTTON_PRESS, START_STREAM, STOP_STREAM, SIGNAL_TICK, PROPERTY_CHANGE, LAST_SIGNAL }; static void totem_embedded_finalize (GObject *object) { TotemEmbedded *embedded = TOTEM_EMBEDDED (object); /* D-Bus */ if (embedded->name_id) g_bus_unown_name (embedded->name_id); g_clear_object (&embedded->conn); g_clear_pointer (&embedded->introspection_data, g_dbus_node_info_unref); /* Interface */ g_clear_object (&embedded->window); g_clear_object (&embedded->xml); g_clear_object (&embedded->menuxml); /* FIXME etc */ G_OBJECT_CLASS (totem_embedded_parent_class)->finalize (object); } static void totem_embedded_class_init (TotemEmbeddedClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = totem_embedded_finalize; } static void totem_embedded_exit (TotemEmbedded *emb) { //FIXME what happens when embedded, and we can't go on? exit (1); } static void totem_embedded_error_and_exit (char *title, char *reason, TotemEmbedded *emb) { /* Avoid any more contacts, so drop off the bus */ if (emb->name_id > 0) { g_bus_unown_name (emb->name_id); emb->name_id = 0; } g_clear_object (&emb->conn); /* FIXME send a signal to the plugin with the error message instead! */ totem_interface_error_blocking (title, reason, GTK_WINDOW (emb->window)); totem_embedded_exit (emb); } static void totem_embedded_volume_changed (TotemEmbedded *emb, double volume) { g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "PropertyChange", g_variant_new ("(sv)", TOTEM_PROPERTY_VOLUME, g_variant_new_double (volume), NULL), NULL); } static void totem_embedded_set_error (TotemEmbedded *emb, int code, char *secondary) { g_clear_error (&emb->error); emb->error = g_error_new_literal (TOTEM_EMBEDDED_ERROR_QUARK, code, secondary); g_message ("totem_embedded_set_error: '%s'", secondary); } static void totem_embedded_set_error_logo (TotemEmbedded *embedded, GDBusMethodInvocation *invocation) { g_message ("totem_embedded_set_error_logo called by browser plugin"); totem_embedded_set_logo_by_name (embedded, IMAGE_MISSING); g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_set_state (TotemEmbedded *emb, TotemStates state) { GtkWidget *image; const gchar *id; if (state == emb->state) return; g_message ("Viewer state: %s", totem_states[state]); image = gtk_button_get_image (GTK_BUTTON (emb->pp_button)); switch (state) { case TOTEM_STATE_STOPPED: id = "media-playback-start-symbolic"; if (emb->href_uri != NULL && emb->hidden == FALSE) { gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (emb->bvw)), emb->cursor); } break; case TOTEM_STATE_PAUSED: id = "media-playback-start-symbolic"; break; case TOTEM_STATE_PLAYING: id = "media-playback-pause-symbolic"; if (emb->href_uri == NULL && emb->hidden == FALSE) { gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (emb->bvw)), NULL); } break; case TOTEM_STATE_INVALID: default: g_assert_not_reached (); break; } if (state == TOTEM_STATE_PLAYING) { if (emb->inhibit_id == 0) { emb->inhibit_id = gtk_application_inhibit (GTK_APPLICATION (emb), NULL, GTK_APPLICATION_INHIBIT_IDLE, _("Playing a movie")); } } else { if (emb->inhibit_id != 0) { gtk_application_uninhibit (GTK_APPLICATION (emb), emb->inhibit_id); emb->inhibit_id = 0; } } gtk_image_set_from_icon_name (GTK_IMAGE (image), id, GTK_ICON_SIZE_MENU); // gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (emb->pp_fs_button), id); emb->state = state; } static void totem_embedded_set_logo_by_name (TotemEmbedded *embedded, const char *name) { totem_embedded_set_state (embedded, TOTEM_STATE_STOPPED); if (embedded->audioonly != FALSE || embedded->hidden != FALSE) return; bacon_video_widget_set_logo (embedded->bvw, name); bacon_video_widget_set_logo_mode (embedded->bvw, TRUE); } static void totem_embedded_set_pp_state (TotemEmbedded *emb, gboolean state) { gtk_widget_set_sensitive (emb->pp_button, state); // gtk_widget_set_sensitive (emb->pp_fs_button, state); } static void totem_embedded_update_title (TotemEmbedded *emb, const char *title) { GObject *obj; GtkWidget *header; obj = bacon_video_widget_get_header_object (emb->bvw); header = g_object_get_data (obj, "header"); gtk_header_bar_set_title (GTK_HEADER_BAR (header), title ? title : ""); } static gboolean totem_embedded_open_internal (TotemEmbedded *emb, gboolean start_play, GError **error) { char *uri; /* FIXME: stop previous content, or is that automatic ? */ if (emb->is_browser_stream) { if (emb->size > 0) uri = g_strdup_printf ("fd://0?size=%"G_GINT64_FORMAT, emb->size); else uri = g_strdup ("fd://0"); } else { uri = g_strdup (emb->current_uri); } if (!uri) { g_set_error_literal (error, TOTEM_EMBEDDED_ERROR_QUARK, TOTEM_EMBEDDED_NO_URI, _("No URI to play")); //FIXME totem_embedded_set_error (emb, error); |error| may be null? return FALSE; } g_message ("totem_embedded_open_internal '%s' subtitle '%s' is-browser-stream %d start-play %d", uri, emb->current_subtitle_uri, emb->is_browser_stream, start_play); bacon_video_widget_set_logo_mode (emb->bvw, FALSE); bacon_video_widget_open (emb->bvw, uri); bacon_video_widget_set_text_subtitle (emb->bvw, emb->current_subtitle_uri); g_free (uri); /* FIXME we shouldn't even do that here */ if (start_play) totem_embedded_play (emb, NULL); else totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), TRUE); totem_embedded_update_menu (emb); totem_embedded_update_title (emb, emb->href_uri ? emb->href_uri : emb->current_uri); return TRUE; } static gboolean totem_embedded_play (TotemEmbedded *emb, GError **error) { GError *err = NULL; if (emb->current_uri == NULL) { totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), FALSE); g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "ButtonPress", g_variant_new ("(uu)", gtk_get_current_event_time (), 0, NULL), NULL); return TRUE; } totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), FALSE); if (bacon_video_widget_play (emb->bvw, &err) != FALSE) { totem_embedded_set_state (emb, TOTEM_STATE_PLAYING); totem_embedded_set_pp_state (emb, TRUE); } else { g_warning ("Error in bacon_video_widget_play: %s", err->message); g_error_free (err); } return TRUE; } static void totem_embedded_pause (TotemEmbedded *emb) { totem_embedded_set_state (emb, TOTEM_STATE_PAUSED); bacon_video_widget_pause (emb->bvw); } static void totem_embedded_stop (TotemEmbedded *emb) { totem_embedded_set_state (emb, TOTEM_STATE_STOPPED); bacon_video_widget_stop (emb->bvw); g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "Tick", g_variant_new ("(uus)", 0, 0, totem_states[emb->state], NULL), NULL); } static void totem_embedded_do_command (TotemEmbedded *embedded, GDBusMethodInvocation *invocation, const char *command) { char *error_msg; g_return_val_if_fail (command != NULL, FALSE); g_message ("totem_embedded_do_command: %s", command); if (strcmp (command, TOTEM_COMMAND_PLAY) == 0) { GError *error = NULL; if (!totem_embedded_play (embedded, &error)) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); return; } if (strcmp (command, TOTEM_COMMAND_PAUSE) == 0) { totem_embedded_pause (embedded); g_dbus_method_invocation_return_value (invocation, NULL); return; } if (strcmp (command, TOTEM_COMMAND_STOP) == 0) { totem_embedded_stop (embedded); g_dbus_method_invocation_return_value (invocation, NULL); return; } error_msg = g_strdup_printf ("Unknown command '%s'", command); g_dbus_method_invocation_return_dbus_error (invocation, "org.freedesktop.DBus.Error.InvalidArgs", error_msg); g_free (error_msg); } static void totem_embedded_set_href (TotemEmbedded *embedded, GDBusMethodInvocation *invocation, const char *href_uri, const char *target) { g_message ("totem_embedded_set_href %s (target: %s)", href_uri, target); g_free (embedded->href_uri); g_free (embedded->target); if (href_uri != NULL) { embedded->href_uri = g_strdup (href_uri); } else { g_clear_pointer (&embedded->href_uri, g_free); gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (embedded->bvw)), NULL); } embedded->target = g_strdup (target); g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_set_volume (TotemEmbedded *embedded, GDBusMethodInvocation *invocation, gdouble volume) { g_message ("totem_embedded_set_volume: %f", volume); bacon_video_widget_set_volume (embedded->bvw, volume); totem_embedded_volume_changed (embedded, volume); g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_launch_player (TotemEmbedded *embedded, GDBusMethodInvocation *invocation, const char *uri, guint32 user_time) { GError *error = NULL; GList *uris; GdkScreen *screen; GdkAppLaunchContext *ctx; gboolean result; g_return_val_if_fail (embedded->app != NULL, FALSE); if (embedded->type == TOTEM_PLUGIN_TYPE_NARROWSPACE && embedded->href_uri != NULL) { uris = g_list_prepend (NULL, embedded->href_uri); } else { uris = g_list_prepend (NULL, embedded->current_uri); } ctx = gdk_display_get_app_launch_context (gtk_widget_get_display (embedded->window)); screen = gtk_widget_get_screen (embedded->window); gdk_app_launch_context_set_screen (ctx, screen); gdk_app_launch_context_set_timestamp (ctx, user_time); gdk_app_launch_context_set_icon (ctx, g_app_info_get_icon (embedded->app)); result = g_app_info_launch_uris (embedded->app, uris, G_APP_LAUNCH_CONTEXT (ctx), &error); if (invocation == NULL) goto end; if (result) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); end: g_list_free (uris); } static void totem_embedded_set_uri (TotemEmbedded *emb, const char *uri, const char *base_uri, const char *subtitle, gboolean is_browser_stream) { GFile *base_gfile; char *old_uri, *old_base, *old_href, *old_subtitle; base_gfile = NULL; old_uri = emb->current_uri; old_subtitle = emb->current_subtitle_uri; old_base = emb->base_uri; old_href = emb->href_uri; emb->base_uri = g_strdup (base_uri); if (base_uri) base_gfile = g_file_new_for_uri (base_uri); emb->current_uri = totem_pl_parser_resolve_uri (base_gfile, uri); if (subtitle != NULL) emb->current_subtitle_uri = totem_pl_parser_resolve_uri (base_gfile, subtitle); else emb->current_subtitle_uri = NULL; if (base_gfile) g_object_unref (base_gfile); emb->is_browser_stream = (is_browser_stream != FALSE); emb->href_uri = NULL; if (uri != NULL) g_print ("totem_embedded_set_uri uri %s base %s => resolved %s (subtitle %s => resolved %s)\n", uri, base_uri, emb->current_uri, subtitle, emb->current_subtitle_uri); else g_print ("Emptying current_uri\n"); if (old_uri != NULL && g_str_has_prefix (old_uri, "fd://") != FALSE) { int fd; fd = strtol (old_uri + strlen ("fd://"), NULL, 0); close (fd); } g_free (old_uri); g_free (old_base); g_free (old_href); g_free (old_subtitle); g_clear_pointer (&emb->stream_uri, g_free); } static void totem_pl_item_free (gpointer data, gpointer user_data) { TotemPlItem *item = (TotemPlItem *) data; if (!item) return; g_free (item->uri); g_free (item->title); g_free (item->subtitle); g_slice_free (TotemPlItem, item); } static void totem_embedded_clear_playlist (TotemEmbedded *emb, GDBusMethodInvocation *invocation) { g_message ("totem_embedded_clear_playlist"); g_list_free_full (emb->playlist, (GDestroyNotify) totem_pl_item_free); emb->playlist = NULL; emb->current = NULL; emb->num_items = 0; totem_embedded_set_uri (emb, NULL, NULL, NULL, FALSE); bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); totem_embedded_update_title (emb, NULL); if (invocation != NULL) g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_add_item (TotemEmbedded *embedded, GDBusMethodInvocation *invocation, const char *base_uri, const char *uri, const char *title, const char *subtitle) { TotemPlItem *item; g_message ("totem_embedded_add_item: %s (base: %s title: %s subtitle: %s)", uri, base_uri, title, subtitle); item = g_slice_new0 (TotemPlItem); item->uri = g_strdup (uri); item->title = g_strdup (title); item->subtitle = g_strdup (subtitle); item->duration = -1; item->starttime = -1; embedded->playlist = g_list_append (embedded->playlist, item); embedded->num_items++; if (embedded->current_uri == NULL) { embedded->current = embedded->playlist; totem_embedded_set_uri (embedded, (const char *) uri, base_uri, subtitle, FALSE); totem_embedded_open_internal (embedded, FALSE, NULL /* FIXME */); } g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_set_fullscreen (TotemEmbedded *emb, gboolean fullscreen_enabled) { GtkAction *fs_action; fs_action = GTK_ACTION (gtk_builder_get_object (emb->menuxml, "fullscreen1")); if (emb->fullscreen == fullscreen_enabled) return; g_message ("totem_embedded_set_fullscreen: %d", fullscreen_enabled); if (fullscreen_enabled == FALSE) { GtkWidget * container; container = GTK_WIDGET (gtk_builder_get_object (emb->xml, "video_box")); emb->fullscreen = FALSE; gtk_widget_reparent (GTK_WIDGET (emb->bvw), container); gtk_widget_hide (emb->fs_window); gtk_action_set_sensitive (fs_action, TRUE); } else { GdkRectangle rect; int monitor; /* Move the fullscreen window to the screen where the * video widget currently is */ monitor = gdk_screen_get_monitor_at_window (gtk_widget_get_screen (GTK_WIDGET (emb->bvw)), gtk_widget_get_window (GTK_WIDGET (emb->bvw))); gdk_screen_get_monitor_geometry (gtk_widget_get_screen (GTK_WIDGET (emb->bvw)), monitor, &rect); gtk_window_move (GTK_WINDOW (emb->fs_window), rect.x, rect.y); totem_interface_set_transient_for (GTK_WINDOW (emb->fs_window), GTK_WINDOW (emb->window)); gtk_widget_reparent (GTK_WIDGET (emb->bvw), emb->fs_window); gtk_window_fullscreen (GTK_WINDOW (emb->fs_window)); emb->fullscreen = TRUE; gtk_widget_show_all (emb->fs_window); gtk_action_set_sensitive (fs_action, FALSE); } g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "PropertyChange", g_variant_new ("(sv)", TOTEM_PROPERTY_ISFULLSCREEN, g_variant_new_boolean (fullscreen_enabled), NULL), NULL); } static void _totem_embedded_set_fullscreen (TotemEmbedded *emb, GDBusMethodInvocation *invocation, gboolean fullscreen_enabled) { totem_embedded_set_fullscreen (emb, fullscreen_enabled); g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_set_time (TotemEmbedded *emb, GDBusMethodInvocation *invocation, guint64 _time) { g_message ("totem_embedded_set_time: %"G_GUINT64_FORMAT, _time); bacon_video_widget_seek_time (emb->bvw, _time, FALSE, NULL); g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_open_uri (TotemEmbedded *emb, GDBusMethodInvocation *invocation, const char *uri, const char *base_uri) { GError *error = NULL; g_message ("totem_embedded_open_uri: uri %s base_uri: %s", uri, base_uri); totem_embedded_clear_playlist (emb, NULL); totem_embedded_set_uri (emb, uri, base_uri, NULL, FALSE); /* We can only have one item in the "playlist" when * we open a particular URI like this */ emb->num_items = 1; if (!totem_embedded_open_internal (emb, TRUE, &error)) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_setup_stream (TotemEmbedded *emb, GDBusMethodInvocation *invocation, const char *uri, const char *base_uri) { g_message ("totem_embedded_setup_stream called: uri %s, base_uri: %s", uri, base_uri); totem_embedded_clear_playlist (emb, NULL); totem_embedded_set_uri (emb, uri, base_uri, NULL, TRUE); /* We can only have one item in the "playlist" when * we open a browser stream */ emb->num_items = 1; /* FIXME: consume any remaining input from stdin */ g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_open_stream (TotemEmbedded *emb, GDBusMethodInvocation *invocation, gint64 size) { GError *error = NULL; g_message ("totem_embedded_open_stream called: with size %"G_GINT64_FORMAT, size); emb->size = size; if (!totem_embedded_open_internal (emb, TRUE, &error)) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_close_stream (TotemEmbedded *emb, GDBusMethodInvocation *invocation) { g_message ("totem_embedded_close_stream"); if (!emb->is_browser_stream) goto end; /* FIXME this enough? */ bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); end: g_dbus_method_invocation_return_value (invocation, NULL); } static gboolean totem_embedded_open_playlist_item (TotemEmbedded *emb, GList *item) { TotemPlItem *plitem; gboolean eop; if (!emb->playlist) return FALSE; eop = (item == NULL); /* Start at the head */ if (item == NULL) item = emb->playlist; /* FIXME: if (emb->current == item) { just start again, depending on repeat/autostart settings } */ emb->current = item; g_assert (item != NULL); plitem = (TotemPlItem *) item->data; totem_embedded_set_uri (emb, (const char *) plitem->uri, emb->base_uri /* FIXME? */, plitem->subtitle, FALSE); bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); //FIXME set the title from the URI if possible totem_embedded_update_title (emb, plitem->title); if (totem_embedded_open_internal (emb, FALSE, NULL /* FIXME */)) { if (plitem->starttime > 0) { gboolean retval; g_message ("Seeking to %d seconds for starttime", plitem->starttime); retval = bacon_video_widget_seek_time (emb->bvw, plitem->starttime * 1000, FALSE, NULL /* FIXME */); if (!retval) return TRUE; } if ((eop != FALSE && emb->repeat != FALSE) || (eop == FALSE)) { totem_embedded_play (emb, NULL); } } return TRUE; } static void totem_embedded_set_local_file (TotemEmbedded *emb, GDBusMethodInvocation *invocation, const char *path, const char *uri, const char *base_uri) { GError *error = NULL; char *file_uri; g_message ("Setting the current path to %s (uri: %s base: %s)", path, uri, base_uri); totem_embedded_clear_playlist (emb, NULL); file_uri = g_filename_to_uri (path, NULL, &error); if (!file_uri) { g_dbus_method_invocation_take_error (invocation, error); return; } /* FIXME what about |uri| param?!! */ totem_embedded_set_uri (emb, file_uri, base_uri, emb->current_subtitle_uri, FALSE); g_free (file_uri); if (!totem_embedded_open_internal (emb, TRUE, &error)) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_set_local_cache (TotemEmbedded *emb, GDBusMethodInvocation *invocation, const char *path) { int fd; g_message ("totem_embedded_set_local_cache: %s", path); /* FIXME Should also handle playlists */ if (!emb->is_browser_stream) goto end; /* Keep the temporary file open, so that StreamAsFile * doesn't remove it from under us */ fd = open (path, O_RDONLY); if (fd < 0) { g_message ("Failed to open local cache file '%s': %s", path, g_strerror (errno)); goto end; } emb->stream_uri = emb->current_uri; emb->current_uri = g_strdup_printf ("fd://%d", fd); end: g_dbus_method_invocation_return_value (invocation, NULL); } static void totem_embedded_set_playlist (TotemEmbedded *emb, GDBusMethodInvocation *invocation, const char *path, const char *uri, const char *base_uri) { g_message ("Setting the current playlist to %s (uri: %s base: %s)", path, uri, base_uri); totem_embedded_clear_playlist (emb, NULL); if (path != NULL && *path != '\0') { char *file_uri; char *tmp_file; GError *err = NULL; GFile *src, *dst; int fd; /* FIXME, we should remove that when we can * parse from memory or * https://bugzilla.gnome.org/show_bug.cgi?id=598702 is fixed */ fd = g_file_open_tmp ("totem-browser-plugin-playlist-XXXXXX", &tmp_file, &err); if (fd < 0) { g_warning ("Couldn't open temporary file for playlist: %s", err->message); g_error_free (err); goto end; } src = g_file_new_for_path (path); dst = g_file_new_for_path (tmp_file); if (g_file_copy (src, dst, G_FILE_COPY_OVERWRITE | G_FILE_COPY_TARGET_DEFAULT_PERMS, NULL, NULL, NULL, &err) == FALSE) { g_warning ("Failed to copy playlist '%s' to '%s': %s", path, tmp_file, err->message); g_error_free (err); g_object_unref (src); g_object_unref (dst); g_free (tmp_file); close (fd); goto end; } g_free (tmp_file); file_uri = g_file_get_uri (dst); g_object_unref (src); g_object_unref (dst); close (fd); emb->remove_copy = TRUE; totem_embedded_set_uri (emb, file_uri, base_uri, NULL, FALSE); g_free (file_uri); } else { totem_embedded_set_uri (emb, uri, base_uri, NULL, FALSE); } /* Schedule parsing on idle */ if (emb->parser_id == 0) emb->parser_id = g_idle_add (totem_embedded_push_parser, emb); end: g_dbus_method_invocation_return_value (invocation, NULL); } static GAppInfo * totem_embedded_get_app_for_uri (const char *uri) { char *type; GAppInfo *info; type = g_content_type_guess (uri, NULL, 0, NULL); info = g_app_info_get_default_for_type (type, TRUE); g_free (type); return info; } static void totem_embedded_update_menu (TotemEmbedded *emb) { GtkWidget *item; GtkMenuShell *menu; char *label; g_clear_object (&emb->menu_item); g_clear_object (&emb->app); if (emb->mimetype && strcmp (emb->mimetype, "application/octet-stream") != 0) { emb->app = g_app_info_get_default_for_type (emb->mimetype, TRUE); } else { const char *uri; /* If we're reading a local file, that's not a playlist, * we need to use that to get its proper mime-type */ if (emb->stream_uri != NULL) uri = emb->stream_uri; else uri = emb->current_uri; emb->app = totem_embedded_get_app_for_uri (uri); } if (emb->app == NULL) { if (emb->mimetype != NULL) { g_message ("Mimetype '%s' doesn't have a handler", emb->mimetype); } else { char *type; type = g_content_type_guess (emb->current_uri, NULL, 0, NULL); g_message ("No handler for URI '%s' (guessed mime-type '%s')", emb->current_uri, type); g_free (type); } return; } /* translators: this is: * Open With ApplicationName * as in nautilus' right-click menu */ label = g_strdup_printf (_("_Open with \"%s\""), g_app_info_get_name (emb->app)); item = gtk_menu_item_new_with_mnemonic (label); g_free (label); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (on_open1_activate), emb); gtk_widget_show (item); emb->menu_item = item; menu = GTK_MENU_SHELL (gtk_builder_get_object (emb->menuxml, "menu")); gtk_menu_shell_insert (menu, item, 1); } static void on_open1_activate (GtkButton *button, TotemEmbedded *emb) { GTimeVal val; g_get_current_time (&val); totem_embedded_launch_player (emb, NULL, NULL, val.tv_sec); totem_embedded_stop (emb); } void on_fullscreen1_activate (GtkMenuItem *menuitem, TotemEmbedded *emb) { totem_embedded_set_fullscreen (emb, TRUE); } void on_copy_location1_activate (GtkButton *button, TotemEmbedded *emb) { GdkDisplay *display; GtkClipboard *clip; const char *uri; if (emb->stream_uri != NULL) { uri = emb->stream_uri; } else if (emb->href_uri != NULL) { uri = emb->href_uri; } else { uri = emb->current_uri; } display = gtk_widget_get_display (GTK_WIDGET (emb->window)); /* Set both the middle-click and the super-paste buffers */ clip = gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clip, uri, -1); clip = gtk_clipboard_get_for_display (display, GDK_SELECTION_PRIMARY); gtk_clipboard_set_text (clip, uri, -1); } void on_preferences1_activate (GtkButton *button, TotemEmbedded *emb) { /* TODO: */ } static void on_play_pause (GtkWidget *widget, TotemEmbedded *emb) { if (emb->state == TOTEM_STATE_PLAYING) { totem_embedded_pause (emb); } else { if (emb->current_uri == NULL) { totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), FALSE); g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "ButtonPress", g_variant_new ("(uu)", gtk_get_current_event_time (), 0, NULL), NULL); } else { totem_embedded_play (emb, NULL); } } } static char * resolve_redirect (const char *old_mrl, const char *mrl) { GFile *old_file, *parent, *new_file; char *retval; /* Get the parent for the current MRL, that's our base */ old_file = g_file_new_for_uri (old_mrl); parent = g_file_get_parent (old_file); g_object_unref (old_file); /* Resolve the URL */ new_file = g_file_get_child (parent, mrl); g_object_unref (parent); retval = g_file_get_uri (new_file); g_object_unref (new_file); return retval; } static void on_got_redirect (GtkWidget *bvw, const char *mrl, TotemEmbedded *emb) { char *new_uri; g_message ("stream uri: %s", emb->stream_uri ? emb->stream_uri : "(null)"); g_message ("current uri: %s", emb->current_uri ? emb->current_uri : "(null)"); g_message ("base uri: %s", emb->base_uri ? emb->base_uri : "(null)"); g_message ("redirect: %s", mrl); bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); /* If we don't have a relative URI */ if (strstr (mrl, "://") != NULL) new_uri = NULL; /* We are using a local cache, so we resolve against the stream_uri */ else if (emb->stream_uri) new_uri = resolve_redirect (emb->stream_uri, mrl); /* We don't have a local cache, so resolve against the URI */ else if (emb->current_uri) new_uri = resolve_redirect (emb->current_uri, mrl); /* FIXME: not sure that this is actually correct... */ else if (emb->base_uri) new_uri = resolve_redirect (emb->base_uri, mrl); else new_uri = NULL; g_message ("Redirecting to '%s'", new_uri ? new_uri : mrl); /* FIXME: clear playlist? or replace current entry? or add a new entry? */ /* FIXME: use totem_embedded_open_uri? */ totem_embedded_set_uri (emb, new_uri ? new_uri : mrl , emb->base_uri /* FIXME? */, emb->current_subtitle_uri, FALSE); totem_embedded_set_state (emb, TOTEM_STATE_STOPPED); totem_embedded_open_internal (emb, TRUE, NULL /* FIXME? */); g_free (new_uri); } static char * totem_embedded_get_nice_name_for_stream (BaconVideoWidget *bvw) { char *title, *artist, *retval; int tracknum; GValue value = { 0, }; bacon_video_widget_get_metadata (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 (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 (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 on_got_metadata (BaconVideoWidget *bvw, TotemEmbedded *emb) { char *title; title = totem_embedded_get_nice_name_for_stream (bvw); if (title == NULL) return; totem_embedded_update_title (emb, title); } static void totem_embedded_toggle_fullscreen (TotemEmbedded *emb) { totem_embedded_set_fullscreen (emb, !emb->fullscreen); } static void totem_embedded_on_fullscreen_exit (GtkWidget *widget, TotemEmbedded *emb) { totem_embedded_set_fullscreen (emb, FALSE); } static gboolean on_video_button_press_event (BaconVideoWidget *bvw, GdkEventButton *event, TotemEmbedded *emb) { guint state = event->state & gtk_accelerator_get_default_mod_mask (); GtkMenu *menu; //g_print ("button-press type %d button %d state %d play-state %d\n", // event->type, event->button, state, emb->state); menu = GTK_MENU (gtk_builder_get_object (emb->menuxml, "menu")); if (event->type == GDK_BUTTON_PRESS && event->button == 1 && state == 0 && emb->state == TOTEM_STATE_STOPPED) { if (emb->error != NULL) { totem_interface_error (_("An error occurred"), emb->error->message, (GtkWindow *) (emb->window)); g_clear_error (&emb->error); } else if (!gtk_widget_get_visible (GTK_WIDGET (menu))) { g_message ("emitting signal button press"); g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "ButtonPress", g_variant_new ("(uu)", gtk_get_current_event_time (), 0, NULL), NULL); } else { gtk_menu_popdown (menu); } } else if (event->type == GDK_BUTTON_PRESS && event->button == 3 && state == 0) { GtkWidget *popup_button; popup_button = GTK_WIDGET (gtk_builder_get_object (emb->xml, "popup_button")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (popup_button), FALSE); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time); } else if (event->type == GDK_2BUTTON_PRESS && event->button == 1 && state == 0) { totem_embedded_toggle_fullscreen (emb); } return FALSE; } static void on_eos_event (BaconVideoWidget *bvw, TotemEmbedded *emb) { gboolean start_play; totem_embedded_set_state (emb, TOTEM_STATE_STOPPED); gtk_adjustment_set_value (emb->seekadj, 0); /* FIXME: the plugin needs to handle EOS itself, e.g. for QTNext */ start_play = (emb->repeat != FALSE && emb->autostart); /* No playlist if we have fd://0, right? */ if (emb->is_browser_stream) { /* Verify that we had a SetLocalCache */ if (g_str_has_prefix (emb->current_uri, "file://") != FALSE) { emb->num_items = 1; emb->is_browser_stream = FALSE; bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); totem_embedded_open_internal (emb, start_play, NULL /* FIXME? */); } else { /* FIXME: should find a way to enable playback of the stream again without re-requesting it */ totem_embedded_set_pp_state (emb, FALSE); } /* FIXME? else if (emb->playing_nth_item == emb->playlist_num_items) ? */ } else if (emb->num_items == 1) { if (g_str_has_prefix (emb->current_uri, "file://") != FALSE) { if (bacon_video_widget_is_seekable (emb->bvw) != FALSE) { bacon_video_widget_pause (emb->bvw); bacon_video_widget_seek (emb->bvw, 0.0, NULL); if (start_play != FALSE) totem_embedded_play (emb, NULL); } else { bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); totem_embedded_open_internal (emb, start_play, NULL /* FIXME? */); } } else { bacon_video_widget_close (emb->bvw); update_fill (emb, -1.0); totem_embedded_open_internal (emb, start_play, NULL /* FIXME? */); } } else if (emb->current) { totem_embedded_open_playlist_item (emb, emb->current->next); } } static gboolean skip_unplayable_stream (TotemEmbedded *emb) { on_eos_event (BACON_VIDEO_WIDGET (emb->bvw), emb); return FALSE; } static void on_error_event (BaconVideoWidget *bvw, char *message, gboolean playback_stopped, TotemEmbedded *emb) { if (playback_stopped) { /* FIXME: necessary? */ if (emb->is_browser_stream) g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "StopStream", NULL, NULL); totem_embedded_set_state (emb, TOTEM_STATE_STOPPED); /* If we have a playlist, and that the current item * is < 60 seconds long, just go through it * * Same thing for all the items in a non-repeat playlist, * other than the last one * * FIXME we should mark streams as not playable though * so we don't loop through unplayable streams... */ if (emb->num_items > 1 && emb->current) { TotemPlItem *item = emb->current->data; if ((item->duration > 0 && item->duration < 60) || (!emb->repeat && emb->current->next)) { g_idle_add ((GSourceFunc) skip_unplayable_stream, emb); return; } } } totem_embedded_set_error (emb, BVW_ERROR_GENERIC, message); totem_embedded_set_logo_by_name (emb, IMAGE_MISSING); } static void cb_vol (GtkWidget *val, gdouble value, TotemEmbedded *emb) { bacon_video_widget_set_volume (emb->bvw, value); totem_embedded_volume_changed (emb, value); } static void on_tick (GtkWidget *bvw, gint64 current_time, gint64 stream_length, double current_position, gboolean seekable, TotemEmbedded *emb) { if (emb->state != TOTEM_STATE_STOPPED) { gtk_widget_set_sensitive (emb->seek, seekable); //gtk_widget_set_sensitive (emb->fs->seek, seekable); if (emb->seeking == FALSE) gtk_adjustment_set_value (emb->seekadj, current_position * 65535); } g_dbus_connection_emit_signal (emb->conn, NULL, TOTEM_PLUGIN_VIEWER_DBUS_PATH, "org.gnome.totem.PluginViewer", "Tick", g_variant_new ("(uus)", (guint32) current_time, (guint32) stream_length, totem_states[emb->state], NULL), NULL); } static void on_buffering (BaconVideoWidget *bvw, gdouble percentage, TotemEmbedded *emb) { // g_message ("Buffering: %d %%", percentage); } static void update_fill (TotemEmbedded *emb, gdouble level) { if (level < 0.0) { gtk_range_set_show_fill_level (GTK_RANGE (emb->seek), FALSE); // gtk_range_set_show_fill_level (GTK_RANGE (emb->fs->seek), FALSE); } else { gtk_range_set_fill_level (GTK_RANGE (emb->seek), level * 65535.0f); gtk_range_set_show_fill_level (GTK_RANGE (emb->seek), TRUE); // gtk_range_set_fill_level (GTK_RANGE (emb->fs->seek), level * 65535.0f); // gtk_range_set_show_fill_level (GTK_RANGE (emb->fs->seek), TRUE); } } static void on_download_buffering (BaconVideoWidget *bvw, gdouble level, TotemEmbedded *emb) { update_fill (emb, level); } static void property_notify_cb_volume (BaconVideoWidget *bvw, GParamSpec *spec, TotemEmbedded *emb) { double volume; volume = bacon_video_widget_get_volume (emb->bvw); g_signal_handlers_block_by_func (emb->volume, cb_vol, emb); gtk_scale_button_set_value (GTK_SCALE_BUTTON (emb->volume), volume); totem_embedded_volume_changed (emb, volume); g_signal_handlers_unblock_by_func (emb->volume, cb_vol, emb); } static gboolean on_seek_start (GtkWidget *widget, GdkEventButton *event, TotemEmbedded *emb) { emb->seeking = TRUE; // totem_time_label_set_seeking (TOTEM_TIME_LABEL (emb->fs->time_label), // TRUE); return FALSE; } static gboolean cb_on_seek (GtkWidget *widget, GdkEventButton *event, TotemEmbedded *emb) { bacon_video_widget_seek (emb->bvw, gtk_range_get_value (GTK_RANGE (widget)) / 65535, NULL); // totem_time_label_set_seeking (TOTEM_TIME_LABEL (emb->fs->time_label), // FALSE); emb->seeking = FALSE; return FALSE; } #ifdef GNOME_ENABLE_DEBUG static void controls_size_allocate_cb (GtkWidget *controls, GtkAllocation *allocation, gpointer data) { g_message ("Viewer: Controls height is %dpx", allocation->height); g_signal_handlers_disconnect_by_func (controls, G_CALLBACK (controls_size_allocate_cb), NULL); } #endif static void totem_embedded_action_volume_relative (TotemEmbedded *emb, double off_pct) { double vol; if (bacon_video_widget_can_set_volume (emb->bvw) == FALSE) return; vol = bacon_video_widget_get_volume (emb->bvw); bacon_video_widget_set_volume (emb->bvw, vol + off_pct); totem_embedded_volume_changed (emb, vol + off_pct); } static gboolean totem_embedded_handle_key_press (TotemEmbedded *emb, GdkEventKey *event) { switch (event->keyval) { case GDK_KEY_Escape: totem_embedded_set_fullscreen (emb, FALSE); return TRUE; case GDK_KEY_F11: case GDK_KEY_f: case GDK_KEY_F: totem_embedded_toggle_fullscreen (emb); return TRUE; case GDK_KEY_space: on_play_pause (NULL, emb); return TRUE; case GDK_KEY_Up: totem_embedded_action_volume_relative (emb, VOLUME_UP_OFFSET); return TRUE; case GDK_KEY_Down: totem_embedded_action_volume_relative (emb, VOLUME_DOWN_OFFSET); return TRUE; case GDK_KEY_Left: case GDK_KEY_Right: /* FIXME: */ default: return FALSE; } } static gboolean totem_embedded_key_press_event (GtkWidget *widget, GdkEventKey *event, TotemEmbedded *emb) { if (event->type != GDK_KEY_PRESS) return FALSE; if (event->state & GDK_CONTROL_MASK) { switch (event->keyval) { case GDK_KEY_Left: case GDK_KEY_Right: return totem_embedded_handle_key_press (emb, event); default: break; } } if (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 totem_embedded_handle_key_press (emb, event); } static gboolean totem_embedded_construct (TotemEmbedded *emb, Window xid, int width, int height) { GtkWidget *child, *container, *image; GtkWidget *popup_button; GError *err = NULL; emb->xml = totem_interface_load ("mozilla-viewer.ui", TRUE, GTK_WINDOW (emb->window), emb); g_assert (emb->xml); if (xid != 0) { g_assert (!emb->hidden); emb->window = gtk_plug_new (xid); /* Can't do anything before it's realized */ gtk_widget_realize (emb->window); child = GTK_WIDGET (gtk_builder_get_object (emb->xml, "content_box")); gtk_container_add (GTK_CONTAINER (emb->window), child); } else { g_assert (emb->hidden); emb->window = GTK_WIDGET (gtk_builder_get_object (emb->xml, "window")); } /* if (emb->hidden || emb->audioonly != FALSE) * FIXME disable video decoding */ emb->bvw = BACON_VIDEO_WIDGET (bacon_video_widget_new (&err)); /* FIXME: check the UA strings of the legacy plugins themselves */ /* FIXME: at least hxplayer seems to send different UAs depending on the protocol!? */ if (emb->user_agent != NULL) { bacon_video_widget_set_user_agent (emb->bvw, emb->user_agent); g_clear_pointer (&emb->user_agent, g_free); } /* FIXMEchpe: this is just the initial value. I think in case e.g. we're doing * a playlist, the playlist's URI should become the referrer? */ g_print ("Referrer URI: %s\n", emb->referrer_uri); if (emb->referrer_uri != NULL) { bacon_video_widget_set_referrer (emb->bvw, emb->referrer_uri); g_clear_pointer (&emb->referrer_uri, g_free); } /* Fullscreen setup */ emb->fs_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_realize (emb->fs_window); gtk_window_set_title (GTK_WINDOW (emb->fs_window), _("Totem Movie Player")); gtk_window_set_default_icon_name ("totem"); /* FIXME add exit fullscreen button g_signal_connect (G_OBJECT (emb->fs->exit_button), "clicked", G_CALLBACK (totem_embedded_on_fullscreen_exit), emb); */ /* FIXME make popup controls work emb->pp_fs_button = GTK_WIDGET (gtk_tool_button_new_from_stock ("media-playback-start-symbolic")); g_signal_connect (G_OBJECT (emb->pp_fs_button), "clicked", G_CALLBACK (on_play_pause), emb); gtk_container_add (GTK_CONTAINER (emb->fs->buttons_box), emb->pp_fs_button); */ /* Connect the keys */ gtk_widget_add_events (emb->fs_window, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); g_signal_connect (G_OBJECT(emb->fs_window), "key_press_event", G_CALLBACK (totem_embedded_key_press_event), emb); g_signal_connect (G_OBJECT(emb->fs_window), "key_release_event", G_CALLBACK (totem_embedded_key_press_event), emb); gtk_widget_add_events (GTK_WIDGET (emb->bvw), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); g_signal_connect (G_OBJECT(emb->bvw), "key_press_event", G_CALLBACK (totem_embedded_key_press_event), emb); g_signal_connect (G_OBJECT(emb->bvw), "key_release_event", G_CALLBACK (totem_embedded_key_press_event), emb); g_signal_connect (G_OBJECT(emb->bvw), "got-redirect", G_CALLBACK (on_got_redirect), emb); g_signal_connect (G_OBJECT(emb->bvw), "got-metadata", G_CALLBACK (on_got_metadata), emb); g_signal_connect (G_OBJECT (emb->bvw), "eos", G_CALLBACK (on_eos_event), emb); g_signal_connect (G_OBJECT (emb->bvw), "error", G_CALLBACK (on_error_event), emb); g_signal_connect (G_OBJECT(emb->bvw), "button-press-event", G_CALLBACK (on_video_button_press_event), emb); g_signal_connect (G_OBJECT(emb->bvw), "tick", G_CALLBACK (on_tick), emb); g_signal_connect (G_OBJECT (emb->bvw), "buffering", G_CALLBACK (on_buffering), emb); g_signal_connect (G_OBJECT (emb->bvw), "download-buffering", G_CALLBACK (on_download_buffering), emb); g_signal_connect (G_OBJECT (emb->bvw), "notify::volume", G_CALLBACK (property_notify_cb_volume), emb); container = GTK_WIDGET (gtk_builder_get_object (emb->xml, "video_box")); gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (emb->bvw)); /* FIXME: why can't this wait until the whole window is realised? */ gtk_widget_realize (GTK_WIDGET (emb->bvw)); gtk_widget_show (GTK_WIDGET (emb->bvw)); /* FIXME disable video decoding when hidden */ if (emb->audioonly != FALSE) { gtk_widget_hide (container); } emb->seek = GTK_WIDGET (gtk_builder_get_object (emb->xml, "time_hscale")); emb->seekadj = gtk_range_get_adjustment (GTK_RANGE (emb->seek)); // gtk_range_set_adjustment (GTK_RANGE (emb->fs->seek), emb->seekadj); g_signal_connect (emb->seek, "button-press-event", G_CALLBACK (on_seek_start), emb); g_signal_connect (emb->seek, "button-release-event", G_CALLBACK (cb_on_seek), emb); // g_signal_connect (emb->fs->seek, "button-press-event", // G_CALLBACK (on_seek_start), emb); // g_signal_connect (emb->fs->seek, "button-release-event", // G_CALLBACK (cb_on_seek), emb); emb->pp_button = GTK_WIDGET (gtk_builder_get_object (emb->xml, "pp_button")); image = gtk_image_new_from_icon_name ("media-playback-start-symbolic", GTK_ICON_SIZE_MENU); gtk_button_set_image (GTK_BUTTON (emb->pp_button), image); g_signal_connect (G_OBJECT (emb->pp_button), "clicked", G_CALLBACK (on_play_pause), emb); /* popup */ emb->menuxml = totem_interface_load ("mozilla-viewer.ui", TRUE, GTK_WINDOW (emb->window), emb); g_assert (emb->menuxml); popup_button = GTK_WIDGET (gtk_builder_get_object (emb->xml, "popup_button")); gtk_menu_button_set_popup (GTK_MENU_BUTTON (popup_button), (GtkWidget *) gtk_builder_get_object (emb->menuxml, "menu")); gtk_menu_button_set_direction (GTK_MENU_BUTTON (popup_button), GTK_ARROW_UP); emb->volume = GTK_WIDGET (gtk_builder_get_object (emb->xml, "volume_button")); // gtk_scale_button_set_adjustment (GTK_SCALE_BUTTON (emb->fs->volume), // gtk_scale_button_get_adjustment // (GTK_SCALE_BUTTON (emb->volume))); gtk_button_set_focus_on_click (GTK_BUTTON (emb->volume), FALSE); g_signal_connect (G_OBJECT (emb->volume), "value-changed", G_CALLBACK (cb_vol), emb); if (!emb->hidden) gtk_widget_set_size_request (emb->window, width, height); #ifdef GNOME_ENABLE_DEBUG child = GTK_WIDGET (gtk_builder_get_object (emb->xml, "controls")); g_signal_connect_after (G_OBJECT (child), "size-allocate", G_CALLBACK (controls_size_allocate_cb), NULL); #endif if (emb->controller_hidden != FALSE) { child = GTK_WIDGET (gtk_builder_get_object (emb->xml, "controls")); gtk_widget_hide (child); } /* Try to make controls smaller */ { GtkCssProvider *provider; gchar *css_path; GError *error = NULL; css_path = totem_interface_get_full_path ("mozilla-viewer.css"); provider = gtk_css_provider_new (); gtk_css_provider_load_from_path (provider, css_path, &error); g_free (css_path); if (error != NULL) { /* Error loading the CSS */ g_warning ("Couldn't load the CSS file '%s' for the browser plugin. The interface might not be styled correctly.", css_path); g_error_free (error); } else { /* Success! Apply the styling to various widgets */ GtkStyleContext *context; GtkWidget *widget; context = gtk_widget_get_style_context (emb->pp_button); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); context = gtk_widget_get_style_context (GTK_WIDGET (gtk_builder_get_object (emb->xml, "time_hscale"))); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); context = gtk_widget_get_style_context (emb->volume); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); widget = GTK_WIDGET (gtk_builder_get_object (emb->xml, "popup_button")); context = gtk_widget_get_style_context (widget); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } g_object_unref (provider); } /* Create the cursor before setting the state */ if (!emb->hidden) { emb->cursor = gdk_cursor_new_for_display (gtk_widget_get_display (emb->window), GDK_HAND2); } totem_embedded_set_state (emb, TOTEM_STATE_STOPPED); if (!emb->hidden) { gtk_widget_show (emb->window); } /* Set the logo and the button glow */ if (!emb->hidden && emb->autostart == FALSE) { totem_glow_button_set_glow (TOTEM_GLOW_BUTTON (emb->pp_button), TRUE); if (gtk_widget_get_direction (GTK_WIDGET (emb->bvw)) == GTK_TEXT_DIR_LTR) totem_embedded_set_logo_by_name (emb, "gtk-media-play-ltr"); else totem_embedded_set_logo_by_name (emb, "gtk-media-play-rtl"); } else { totem_embedded_set_logo_by_name (emb, "totem"); } return TRUE; } static void totem_embedded_set_window (TotemEmbedded *embedded, GDBusMethodInvocation *invocation, const char *controls, guint window, int width, int height) { g_print ("Viewer: SetWindow XID %u size %d:%d\n", window, width, height); if (strcmp (controls, "All") != 0 && strcmp (controls, "ImageWindow") != 0) { g_dbus_method_invocation_return_error (invocation, TOTEM_EMBEDDED_ERROR_QUARK, TOTEM_EMBEDDED_SETWINDOW_UNSUPPORTED_CONTROLS, "Unsupported controls '%s'", controls); return; } if (embedded->window != NULL) { g_warning ("Viewer: Already have a window!"); g_dbus_method_invocation_return_error (invocation, TOTEM_EMBEDDED_ERROR_QUARK, TOTEM_EMBEDDED_SETWINDOW_HAVE_WINDOW, "Already have a window"); return; } if (window == 0) { g_dbus_method_invocation_return_error (invocation, TOTEM_EMBEDDED_ERROR_QUARK, TOTEM_EMBEDDED_SETWINDOW_INVALID_XID, "Invalid XID"); return; } embedded->width = width; embedded->height = height; totem_embedded_construct (embedded, (Window) window, width, height); g_dbus_method_invocation_return_value (invocation, NULL); } static void entry_metadata_foreach (const char *key, const char *value, gpointer data) { if (g_ascii_strcasecmp (key, "url") == 0) return; g_print ("\t%s = '%s'\n", key, value); } static void entry_parsed (TotemPlParser *parser, const char *uri, GHashTable *metadata, gpointer data) { TotemEmbedded *emb = (TotemEmbedded *) data; TotemPlItem *item; int duration; if (g_str_has_prefix (uri, "file://") != FALSE && g_str_has_prefix (emb->base_uri, "file://") == FALSE) { g_print ("not adding URI '%s' (local file referenced from remote location)\n", uri); return; } g_print ("added URI '%s'\n", uri); g_hash_table_foreach (metadata, (GHFunc) entry_metadata_foreach, NULL); /* Skip short advert streams */ duration = totem_pl_parser_parse_duration (g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_DURATION), FALSE); if (duration == 0) return; item = g_slice_new0 (TotemPlItem); item->uri = g_strdup (uri); item->title = g_strdup (g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE)); item->duration = duration; item->starttime = totem_pl_parser_parse_duration (g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_STARTTIME), FALSE); emb->playlist = g_list_prepend (emb->playlist, item); } static gboolean totem_embedded_push_parser (gpointer data) { TotemEmbedded *emb = (TotemEmbedded *) data; TotemPlParser *parser; TotemPlParserResult res; emb->parser_id = 0; parser = totem_pl_parser_new (); g_object_set (parser, "force", TRUE, "disable-unsafe", TRUE, NULL); g_signal_connect (parser, "entry-parsed", G_CALLBACK (entry_parsed), emb); res = totem_pl_parser_parse_with_base (parser, emb->current_uri, emb->base_uri, FALSE); g_object_unref (parser); /* Delete the temporary file created in * totem_embedded_set_playlist */ if (emb->remove_copy) { GFile *file; file = g_file_new_for_uri (emb->current_uri); g_file_delete (file, NULL, NULL); g_object_unref (file); /* And reset */ emb->remove_copy = FALSE; } /* FIXME: show a proper error message */ switch (res) { case TOTEM_PL_PARSER_RESULT_SUCCESS: /* Success! */ break; case TOTEM_PL_PARSER_RESULT_UNHANDLED: g_print ("url '%s' unhandled\n", emb->current_uri); break; case TOTEM_PL_PARSER_RESULT_ERROR: g_print ("error handling url '%s'\n", emb->current_uri); break; case TOTEM_PL_PARSER_RESULT_IGNORED: g_print ("ignored url '%s'\n", emb->current_uri); break; case TOTEM_PL_PARSER_RESULT_CANCELLED: g_print ("cancelled url '%s'\n", emb->current_uri); break; default: g_assert_not_reached (); ;; } /* Check if we have anything in the playlist now */ if (emb->playlist == NULL && res != TOTEM_PL_PARSER_RESULT_SUCCESS) { g_message ("Couldn't parse playlist '%s'", emb->current_uri); totem_embedded_set_error (emb, BVW_ERROR_EMPTY_FILE, _("No playlist or playlist empty")); totem_embedded_set_logo_by_name (emb, IMAGE_MISSING); return FALSE; } else if (emb->playlist == NULL) { g_message ("Playlist empty"); totem_embedded_set_logo_by_name (emb, "totem"); return FALSE; } emb->playlist = g_list_reverse (emb->playlist); emb->num_items = g_list_length (emb->playlist); /* Launch the first item */ totem_embedded_open_playlist_item (emb, emb->playlist); /* don't run again */ return FALSE; } static void handle_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { TotemEmbedded *emb = (TotemEmbedded *) user_data; if (g_strcmp0 (method_name, "DoCommand") == 0) { char *command; g_variant_get (parameters, "(s)", &command); totem_embedded_do_command (emb, invocation, command); g_free (command); } else if (g_strcmp0 (method_name, "SetWindow") == 0) { char *controls; guint xid; int width, height; g_variant_get (parameters, "(suii)", &controls, &xid, &width, &height); totem_embedded_set_window (emb, invocation, controls, xid, width, height); g_free (controls); } else if (g_strcmp0 (method_name, "SetupStream") == 0) { char *uri, *base_uri; g_variant_get (parameters, "(ss)", &uri, &base_uri); totem_embedded_setup_stream (emb, invocation, uri, base_uri); g_free (uri); g_free (base_uri); } else if (g_strcmp0 (method_name, "OpenStream") == 0) { gint64 size; g_variant_get (parameters, "(x)", &size); totem_embedded_open_stream (emb, invocation, size); } else if (g_strcmp0 (method_name, "CloseStream") == 0) { totem_embedded_close_stream (emb, invocation); } else if (g_strcmp0 (method_name, "OpenURI") == 0) { char *uri, *base_uri; g_variant_get (parameters, "(ss)", &uri, &base_uri); totem_embedded_open_uri (emb, invocation, uri, base_uri); g_free (uri); g_free (base_uri); } else if (g_strcmp0 (method_name, "SetPlaylist") == 0) { char *path, *uri, *base_uri; g_variant_get (parameters, "(sss)", &path, &uri, &base_uri); totem_embedded_set_playlist (emb, invocation, path, uri, base_uri); g_free (path); g_free (uri); g_free (base_uri); } else if (g_strcmp0 (method_name, "SetLocalFile") == 0) { char *path, *uri, *base_uri; g_variant_get (parameters, "(sss)", &path, &uri, &base_uri); totem_embedded_set_local_file (emb, invocation, path, uri, base_uri); g_free (path); g_free (uri); g_free (base_uri); } else if (g_strcmp0 (method_name, "SetLocalCache") == 0) { char *path; g_variant_get (parameters, "(s)", &path); totem_embedded_set_local_cache (emb, invocation, path); g_free (path); } else if (g_strcmp0 (method_name, "SetHref") == 0) { char *href_uri, *target; g_variant_get (parameters, "(ss)", &href_uri, &target); totem_embedded_set_href (emb, invocation, href_uri, target); g_free (href_uri); g_free (target); } else if (g_strcmp0 (method_name, "SetErrorLogo") == 0) { totem_embedded_set_error_logo (emb, invocation); } else if (g_strcmp0 (method_name, "LaunchPlayer") == 0) { char *uri; guint time; /* FIXME the URI parameter is unused in the function, * broken API? */ g_variant_get (parameters, "(su)", &uri, &time); totem_embedded_launch_player (emb, invocation, uri, time); g_free (uri); } else if (g_strcmp0 (method_name, "SetVolume") == 0) { gdouble volume; g_variant_get (parameters, "(d)", &volume); totem_embedded_set_volume (emb, invocation, volume); } else if (g_strcmp0 (method_name, "ClearPlaylist") == 0) { totem_embedded_clear_playlist (emb, invocation); } else if (g_strcmp0 (method_name, "AddItem") == 0) { char *base_uri, *uri, *title, *subtitle; g_variant_get (parameters, "(ssss)", &base_uri, &uri, &title, &subtitle); totem_embedded_add_item (emb, invocation, base_uri, uri, title, subtitle); g_free (base_uri); g_free (uri); g_free (title); g_free (subtitle); } else if (g_strcmp0 (method_name, "SetFullscreen") == 0) { gboolean fullscreen_enabled; g_variant_get (parameters, "(b)", &fullscreen_enabled); _totem_embedded_set_fullscreen (emb, invocation, fullscreen_enabled); } else if (g_strcmp0 (method_name, "SetTime") == 0) { guint64 time; g_variant_get (parameters, "(t)", &time); totem_embedded_set_time (emb, invocation, time); } else { char *error_msg; error_msg = g_strdup_printf ("Unimplemented method call '%s'", method_name); g_dbus_method_invocation_return_dbus_error (invocation, "org.freedesktop.DBus.Error.UnknownMethod", error_msg); g_free (error_msg); } } static const GDBusInterfaceVTable interface_vtable = { handle_method_call, NULL, /* GetProperty */ NULL, /* SetProperty */ }; static char *arg_user_agent = NULL; static char *arg_mime_type = NULL; static char **arg_remaining = NULL; static char *arg_referrer = NULL; static gboolean arg_no_controls = FALSE; static gboolean arg_hidden = FALSE; static gboolean arg_is_playlist = FALSE; static gboolean arg_repeat = FALSE; static gboolean arg_no_autostart = FALSE; static gboolean arg_audioonly = FALSE; static gboolean arg_g_fatal_warnings = FALSE; static TotemPluginType arg_plugin_type = TOTEM_PLUGIN_TYPE_LAST; static gboolean parse_plugin_type (const gchar *option_name, const gchar *value, gpointer data, GError **error) { const char types[TOTEM_PLUGIN_TYPE_LAST][12] = { "gmp", "narrowspace", "mully", "cone", "vegas" }; TotemPluginType type; for (type = 0; type < TOTEM_PLUGIN_TYPE_LAST; ++type) { if (strcmp (value, types[type]) == 0) { arg_plugin_type = type; return TRUE; } } g_print ("Unknown plugin type '%s'\n", value); exit (1); } static GOptionEntry option_entries [] = { { TOTEM_OPTION_PLUGIN_TYPE, 0, 0, G_OPTION_ARG_CALLBACK, parse_plugin_type, NULL, NULL }, { TOTEM_OPTION_USER_AGENT, 0, 0, G_OPTION_ARG_STRING, &arg_user_agent, NULL, NULL }, { TOTEM_OPTION_MIMETYPE, 0, 0, G_OPTION_ARG_STRING, &arg_mime_type, NULL, NULL }, { TOTEM_OPTION_CONTROLS_HIDDEN, 0, 0, G_OPTION_ARG_NONE, &arg_no_controls, NULL, NULL }, { TOTEM_OPTION_HIDDEN, 0, 0, G_OPTION_ARG_NONE, &arg_hidden, NULL, NULL }, { TOTEM_OPTION_PLAYLIST, 0, 0, G_OPTION_ARG_NONE, &arg_is_playlist, NULL, NULL }, { TOTEM_OPTION_REPEAT, 0, 0, G_OPTION_ARG_NONE, &arg_repeat, NULL, NULL }, { TOTEM_OPTION_NOAUTOSTART, 0, 0, G_OPTION_ARG_NONE, &arg_no_autostart, NULL, NULL }, { TOTEM_OPTION_AUDIOONLY, 0, 0, G_OPTION_ARG_NONE, &arg_audioonly, NULL, NULL }, { TOTEM_OPTION_REFERRER, 0, 0, G_OPTION_ARG_STRING, &arg_referrer, NULL, NULL }, { "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &arg_g_fatal_warnings, "Make all warnings fatal", NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY /* STRING? */, &arg_remaining, NULL }, { NULL } }; static gboolean check_options (void) { if (arg_plugin_type == TOTEM_PLUGIN_TYPE_LAST) { g_warning ("Plugin type is required\n"); return FALSE; } return TRUE; } int main (int argc, char **argv) { GOptionContext *context; GOptionGroup *baconoptiongroup; TotemEmbedded *emb; GError *e = NULL; char svcname[256]; GtkSettings *gtk_settings; GBytes *introspection_xml; bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); g_set_application_name (_("Movie browser plugin")); gtk_window_set_default_icon_name ("totem"); #ifdef GNOME_ENABLE_DEBUG { int i; g_print ("Viewer: PID %d, args: ", getpid ()); for (i = 0; i < argc; i++) g_print ("%s ", argv[i]); g_print ("\n"); } #endif if (XInitThreads () == 0) { gtk_init (&argc, &argv); totem_embedded_error_and_exit (_("Could not initialize the thread-safe libraries."), _("Verify your system installation. The Totem plugin will now exit."), NULL); } #ifdef GNOME_ENABLE_DEBUG { const char *env; env = g_getenv ("TOTEM_EMBEDDED_GDB"); if (env && g_ascii_strtoull (env, NULL, 10) == 1) { const char *gdbargv[5]; char *gdb; GError *gdberr = NULL; int gdbargc = 0; gdb = g_strdup_printf ("gdb %s %d", argv[0], getpid ()); gdbargv[gdbargc++] = "/usr/bin/gnome-terminal"; gdbargv[gdbargc++] = "--disable-factory"; gdbargv[gdbargc++] = "-e"; gdbargv[gdbargc++] = gdb; gdbargv[gdbargc++] = NULL; if (!g_spawn_async (NULL, (gchar**) gdbargv, NULL /* env */, 0, NULL, NULL, NULL, &gdberr)) { g_warning ("Failed to spawn debugger: %s", gdberr->message); g_error_free (gdberr); } else { g_print ("Sleeping....\n"); g_usleep (10 * 1000 * 1000); /* 10s */ } g_free (gdb); } } #endif if (gtk_clutter_init (NULL, NULL) != CLUTTER_INIT_SUCCESS) { g_warning ("Could not initialise clutter-gtk"); return 1; } context = g_option_context_new ("- Play audio and video inside a web browser"); baconoptiongroup = bacon_video_widget_get_option_group(); g_option_context_add_main_entries (context, option_entries, GETTEXT_PACKAGE); g_option_context_set_translation_domain(context, GETTEXT_PACKAGE); g_option_context_add_group (context, baconoptiongroup); g_option_context_add_group (context, gtk_get_option_group (TRUE)); if (g_option_context_parse (context, &argc, &argv, &e) == FALSE) { g_print ("Failed to parse options: %s\n", e->message); g_error_free (e); exit (1); } g_resources_register (totem_plugin_viewer_get_resource ()); g_type_ensure (TOTEM_TYPE_GLOW_BUTTON); g_type_ensure (TOTEM_TYPE_TIME_LABEL); if (arg_audioonly != FALSE) g_setenv("PULSE_PROP_media.role", "video", TRUE); else g_setenv("PULSE_PROP_media.role", "music", TRUE); if (arg_g_fatal_warnings) { GLogLevelFlags fatal_mask; fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; g_log_set_always_fatal (fatal_mask); } if (!check_options ()) exit (1); gtk_settings = gtk_settings_get_default (); g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", TRUE, NULL); g_snprintf (svcname, sizeof (svcname), TOTEM_PLUGIN_VIEWER_NAME_TEMPLATE, getpid()); emb = g_object_new (TOTEM_TYPE_EMBEDDED, "application-id", svcname, "flags", G_APPLICATION_NON_UNIQUE, NULL); if (g_application_register (G_APPLICATION (emb), NULL, &e) == FALSE) { g_warning ("Failed to register application: %s", e->message); g_error_free (e); return 1; } emb->state = TOTEM_STATE_INVALID; emb->width = -1; emb->height = -1; emb->controller_hidden = arg_no_controls; emb->current_uri = arg_remaining ? arg_remaining[0] : NULL; emb->mimetype = arg_mime_type; emb->hidden = arg_hidden; emb->is_playlist = arg_is_playlist; emb->repeat = arg_repeat; emb->autostart = !arg_no_autostart; emb->audioonly = arg_audioonly; emb->type = arg_plugin_type; emb->user_agent = arg_user_agent; emb->referrer_uri = arg_referrer; /* If we're hidden, construct a hidden window; * else wait to be plugged in. */ if (emb->hidden) { totem_embedded_construct (emb, 0, -1, -1); } introspection_xml = g_resources_lookup_data ("/org/gnome/totem/org_gnome_totem_PluginViewer.xml", 0, NULL); emb->introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (introspection_xml, NULL), NULL); g_bytes_unref (introspection_xml); if (!(emb->conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &e))) { g_warning ("Failed to get DBUS connection: %s", e->message); g_error_free (e); g_object_unref (emb); exit (1); } g_dbus_connection_register_object (emb->conn, TOTEM_PLUGIN_VIEWER_DBUS_PATH, emb->introspection_data->interfaces[0], &interface_vtable, emb, NULL, NULL); emb->name_id = g_bus_own_name_on_connection (emb->conn, svcname, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, NULL, NULL, NULL); gtk_main (); g_object_unref (emb); g_message ("Viewer [PID %d]: exiting\n", getpid ()); return 0; }