1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
18  *
19  * The Totem project hereby grant permission for non-gpl compatible GStreamer
20  * plugins to be used and distributed together with GStreamer and Totem. This
21  * permission are above and beyond the permissions granted by the GPL license
22  * Totem is covered by.
23  *
24  * Monday 7th February 2005: Christian Schaller: Add exception clause.
25  * See license_change file for details.
26  *
27  */
28 
29 /**
30  * SECTION:totem-object
31  * @short_description: main Totem object
32  * @stability: Unstable
33  * @include: totem.h
34  *
35  * #TotemObject is the core object of Totem; a singleton which controls all Totem's main functions.
36  **/
37 
38 #include "config.h"
39 
40 #include <glib-object.h>
41 #include <gtk/gtk.h>
42 #include <glib/gi18n.h>
43 #include <gdk/gdkkeysyms.h>
44 #include <gio/gio.h>
45 #include <libgd/gd.h>
46 
47 #include <string.h>
48 
49 #include "totem.h"
50 #include "totem-private.h"
51 #include "totem-options.h"
52 #include "totem-plugins-engine.h"
53 #include "totem-playlist.h"
54 #include "totem-grilo.h"
55 #include "bacon-video-widget.h"
56 #include "bacon-time-label.h"
57 #include "totem-menu.h"
58 #include "totem-uri.h"
59 #include "totem-interface.h"
60 #include "totem-preferences.h"
61 #include "totem-session.h"
62 #include "totem-main-toolbar.h"
63 
64 #define WANT_MIME_TYPES 1
65 #include "totem-mime-types.h"
66 #include "totem-uri-schemes.h"
67 
68 #define REWIND_OR_PREVIOUS 4000
69 
70 #define SEEK_FORWARD_SHORT_OFFSET 15
71 #define SEEK_BACKWARD_SHORT_OFFSET -5
72 
73 #define SEEK_FORWARD_LONG_OFFSET 10*60
74 #define SEEK_BACKWARD_LONG_OFFSET -3*60
75 
76 #define DEFAULT_WINDOW_W 650
77 #define DEFAULT_WINDOW_H 500
78 
79 #define TOTEM_SESSION_SAVE_TIMEOUT 10 /* seconds */
80 
81 /* casts are to shut gcc up */
82 static const GtkTargetEntry target_table[] = {
83 	{ (gchar*) "text/uri-list", 0, 0 },
84 	{ (gchar*) "_NETSCAPE_URL", 0, 1 }
85 };
86 
87 static gboolean totem_object_open_files_list (TotemObject *totem, GSList *list);
88 static void update_buttons (TotemObject *totem);
89 static void update_fill (TotemObject *totem, gdouble level);
90 static void update_media_menu_items (TotemObject *totem);
91 static void playlist_changed_cb (GtkWidget *playlist, TotemObject *totem);
92 static void play_pause_set_label (TotemObject *totem, TotemStates state);
93 static void totem_object_set_mrl_and_play (TotemObject *totem, const char *mrl, const char *subtitle);
94 
95 /* Callback functions for GtkBuilder */
96 G_MODULE_EXPORT gboolean main_window_destroy_cb (GtkWidget *widget, GdkEvent *event, TotemObject *totem);
97 G_MODULE_EXPORT gboolean window_state_event_cb (GtkWidget *window, GdkEventWindowState *event, TotemObject *totem);
98 G_MODULE_EXPORT gboolean seek_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, TotemObject *totem);
99 G_MODULE_EXPORT void seek_slider_changed_cb (GtkAdjustment *adj, TotemObject *totem);
100 G_MODULE_EXPORT gboolean seek_slider_released_cb (GtkWidget *widget, GdkEventButton *event, TotemObject *totem);
101 G_MODULE_EXPORT gboolean window_key_press_event_cb (GtkWidget *win, GdkEventKey *event, TotemObject *totem);
102 
103 enum {
104 	PROP_0,
105 	PROP_FULLSCREEN,
106 	PROP_PLAYING,
107 	PROP_STREAM_LENGTH,
108 	PROP_SEEKABLE,
109 	PROP_CURRENT_TIME,
110 	PROP_CURRENT_MRL,
111 	PROP_CURRENT_CONTENT_TYPE,
112 	PROP_CURRENT_DISPLAY_NAME,
113 	PROP_MAIN_PAGE
114 };
115 
116 enum {
117 	FILE_OPENED,
118 	FILE_CLOSED,
119 	FILE_HAS_PLAYED,
120 	METADATA_UPDATED,
121 	GET_USER_AGENT,
122 	GET_TEXT_SUBTITLE,
123 	LAST_SIGNAL
124 };
125 
126 static void totem_object_get_property		(GObject *object,
127 						 guint property_id,
128 						 GValue *value,
129 						 GParamSpec *pspec);
130 static void totem_object_finalize (GObject *totem);
131 
132 static int totem_table_signals[LAST_SIGNAL] = { 0 };
133 
G_DEFINE_TYPE(TotemObject,totem_object,GTK_TYPE_APPLICATION)134 G_DEFINE_TYPE(TotemObject, totem_object, GTK_TYPE_APPLICATION)
135 
136 static void
137 totem_object_app_open (GApplication  *application,
138 		       GFile        **files,
139 		       gint           n_files,
140 		       const char    *hint)
141 {
142 	GSList *slist = NULL;
143 	Totem *totem = TOTEM_OBJECT (application);
144 	int i;
145 
146 	optionstate.had_filenames = (n_files > 0);
147 
148 	g_application_activate (application);
149 	gtk_window_present_with_time (GTK_WINDOW (totem->win),
150 				      gtk_get_current_event_time ());
151 
152 	totem_object_set_main_page (TOTEM_OBJECT (application), "player");
153 
154 	for (i = 0 ; i < n_files; i++)
155 		slist = g_slist_prepend (slist, g_file_get_uri (files[i]));
156 
157 	slist = g_slist_reverse (slist);
158 	totem_object_open_files_list (TOTEM_OBJECT (application), slist);
159 	g_slist_free_full (slist, g_free);
160 }
161 
162 static void
totem_object_app_activate(GApplication * app)163 totem_object_app_activate (GApplication *app)
164 {
165 	Totem *totem;
166 
167 	totem = TOTEM_OBJECT (app);
168 
169 	/* Already init'ed? */
170 	if (totem->xml != NULL)
171 		return;
172 
173 	/* Main window */
174 	totem->xml = totem_interface_load ("totem.ui", TRUE, NULL, totem);
175 	if (totem->xml == NULL)
176 		totem_object_exit (NULL);
177 
178 	totem->win = GTK_WIDGET (gtk_builder_get_object (totem->xml, "totem_main_window"));
179 
180 	/* Menubar */
181 	totem->stack = GTK_WIDGET (gtk_builder_get_object (totem->xml, "tmw_main_stack"));
182 
183 	/* The playlist widget */
184 	playlist_widget_setup (totem);
185 
186 	/* The rest of the widgets */
187 	totem->state = STATE_STOPPED;
188 
189 	totem->seek_lock = FALSE;
190 
191 	totem_setup_file_monitoring (totem);
192 	totem_setup_file_filters ();
193 	totem_app_menu_setup (totem);
194 	/* totem_callback_connect (totem); XXX we do this later now, so it might look ugly for a short while */
195 
196 	totem_setup_window (totem);
197 
198 	/* Show ! (again) the video widget this time. */
199 	video_widget_create (totem);
200 	grilo_widget_setup (totem);
201 
202 	/* Show ! */
203 	gtk_widget_show (totem->win);
204 	g_application_mark_busy (G_APPLICATION (totem));
205 
206 	totem->controls_visibility = TOTEM_CONTROLS_UNDEFINED;
207 
208 	totem->seek = g_object_get_data (totem->controls, "seek_scale");
209 	totem->seekadj = gtk_range_get_adjustment (GTK_RANGE (totem->seek));
210 	totem->volume = g_object_get_data (totem->controls, "volume_button");
211 	totem->time_label = g_object_get_data (totem->controls, "time_label");
212 	totem->time_rem_label = g_object_get_data (totem->controls, "time_rem_label");
213 	totem->pause_start = optionstate.pause;
214 
215 	totem_callback_connect (totem);
216 
217 	gtk_widget_grab_focus (GTK_WIDGET (totem->bvw));
218 
219 	/* The prefs after the video widget is connected */
220 	totem->prefs_xml = totem_interface_load ("preferences.ui", TRUE, NULL, totem);
221 	totem->prefs = GTK_WIDGET (gtk_builder_get_object (totem->prefs_xml, "totem_preferences_window"));
222 
223 	gtk_window_set_modal (GTK_WINDOW (totem->prefs), TRUE);
224 	gtk_window_set_transient_for (GTK_WINDOW (totem->prefs), GTK_WINDOW(totem->win));
225 
226 	totem_setup_preferences (totem);
227 
228 	/* Initialise all the plugins, and set the default page, in case
229 	 * it comes from a plugin */
230 	totem_object_plugins_init (totem);
231 
232 	/* We're only supposed to be called from totem_object_app_handle_local_options()
233 	 * and totem_object_app_open() */
234 	g_assert (optionstate.filenames == NULL);
235 
236 	if (!optionstate.had_filenames) {
237 		if (totem_session_try_restore (totem) == FALSE) {
238 			totem_object_set_main_page (totem, "grilo");
239 			totem_object_set_mrl (totem, NULL, NULL);
240 		} else {
241 			totem_object_set_main_page (totem, "player");
242 		}
243 	} else {
244 		totem_object_set_main_page (totem, "player");
245 	}
246 
247 	optionstate.had_filenames = FALSE;
248 
249 	if (optionstate.fullscreen != FALSE) {
250 		if (g_strcmp0 (totem_object_get_main_page (totem), "player") == 0)
251 			totem_object_set_fullscreen (totem, TRUE);
252 	}
253 
254 	/* Set the logo at the last minute so we won't try to show it before a video */
255 	bacon_video_widget_set_logo (totem->bvw, APPLICATION_ID);
256 
257 	g_application_unmark_busy (G_APPLICATION (totem));
258 
259 	gtk_window_set_application (GTK_WINDOW (totem->win), GTK_APPLICATION (totem));
260 }
261 
262 static int
totem_object_app_handle_local_options(GApplication * application,GVariantDict * options)263 totem_object_app_handle_local_options (GApplication *application,
264 				       GVariantDict *options)
265 {
266 	GError *error = NULL;
267 
268 	if (!g_application_register (application, NULL, &error)) {
269 		g_warning ("Failed to register application: %s", error->message);
270 		g_error_free (error);
271 		return 1;
272 	}
273 	totem_options_process_for_server (TOTEM_OBJECT (application), &optionstate);
274 	return 0;
275 }
276 
277 static gboolean
accumulator_first_non_null_wins(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer data)278 accumulator_first_non_null_wins (GSignalInvocationHint *ihint,
279 				 GValue *return_accu,
280 				 const GValue *handler_return,
281 				 gpointer data)
282 {
283 	const gchar *str;
284 
285 	str = g_value_get_string (handler_return);
286 	if (str == NULL)
287 		return TRUE;
288 	g_value_set_string (return_accu, str);
289 
290 	return FALSE;
291 }
292 
293 static void
totem_object_class_init(TotemObjectClass * klass)294 totem_object_class_init (TotemObjectClass *klass)
295 {
296 	GObjectClass *object_class;
297 	GApplicationClass *app_class;
298 
299 	object_class = (GObjectClass *) klass;
300 	app_class = (GApplicationClass *) klass;
301 
302 	object_class->get_property = totem_object_get_property;
303 	object_class->finalize = totem_object_finalize;
304 
305 	app_class->activate = totem_object_app_activate;
306 	app_class->open = totem_object_app_open;
307 	app_class->handle_local_options = totem_object_app_handle_local_options;
308 
309 	/**
310 	 * TotemObject:fullscreen:
311 	 *
312 	 * If %TRUE, Totem is in fullscreen mode.
313 	 **/
314 	g_object_class_install_property (object_class, PROP_FULLSCREEN,
315 					 g_param_spec_boolean ("fullscreen", "Fullscreen?", "Whether Totem is in fullscreen mode.",
316 							       FALSE, G_PARAM_READABLE));
317 
318 	/**
319 	 * TotemObject:playing:
320 	 *
321 	 * If %TRUE, Totem is playing an audio or video file.
322 	 **/
323 	g_object_class_install_property (object_class, PROP_PLAYING,
324 					 g_param_spec_boolean ("playing", "Playing?", "Whether Totem is currently playing a file.",
325 							       FALSE, G_PARAM_READABLE));
326 
327 	/**
328 	 * TotemObject:stream-length:
329 	 *
330 	 * The length of the current stream, in milliseconds.
331 	 **/
332 	g_object_class_install_property (object_class, PROP_STREAM_LENGTH,
333 					 g_param_spec_int64 ("stream-length", "Stream length", "The length of the current stream.",
334 							     G_MININT64, G_MAXINT64, 0,
335 							     G_PARAM_READABLE));
336 
337 	/**
338 	 * TotemObject:current-time:
339 	 *
340 	 * The player's position (time) in the current stream, in milliseconds.
341 	 **/
342 	g_object_class_install_property (object_class, PROP_CURRENT_TIME,
343 					 g_param_spec_int64 ("current-time", "Current time", "The player's position (time) in the current stream.",
344 							     G_MININT64, G_MAXINT64, 0,
345 							     G_PARAM_READABLE));
346 
347 	/**
348 	 * TotemObject:seekable:
349 	 *
350 	 * If %TRUE, the current stream is seekable.
351 	 **/
352 	g_object_class_install_property (object_class, PROP_SEEKABLE,
353 					 g_param_spec_boolean ("seekable", "Seekable?", "Whether the current stream is seekable.",
354 							       FALSE, G_PARAM_READABLE));
355 
356 	/**
357 	 * TotemObject:current-mrl:
358 	 *
359 	 * The MRL of the current stream.
360 	 **/
361 	g_object_class_install_property (object_class, PROP_CURRENT_MRL,
362 					 g_param_spec_string ("current-mrl", "Current MRL", "The MRL of the current stream.",
363 							      NULL, G_PARAM_READABLE));
364 
365 	/**
366 	 * TotemObject:current-content-type:
367 	 *
368 	 * The content-type of the current stream.
369 	 **/
370 	g_object_class_install_property (object_class, PROP_CURRENT_CONTENT_TYPE,
371 					 g_param_spec_string ("current-content-type",
372 							      "Current stream's content-type",
373 							      "Current stream's content-type.",
374 							      NULL, G_PARAM_READABLE));
375 
376 	/**
377 	 * TotemObject:current-display-name:
378 	 *
379 	 * The display name of the current stream.
380 	 **/
381 	g_object_class_install_property (object_class, PROP_CURRENT_DISPLAY_NAME,
382 					 g_param_spec_string ("current-display-name",
383 							      "Current stream's display name",
384 							      "Current stream's display name.",
385 							      NULL, G_PARAM_READABLE));
386 
387 	/**
388 	 * TotemObject:main-page:
389 	 *
390 	 * The name of the current main page (usually "grilo", or "player").
391 	 **/
392 	g_object_class_install_property (object_class, PROP_MAIN_PAGE,
393 					 g_param_spec_string ("main-page",
394 							      "Current main page",
395 							      "Current main page.",
396 							      NULL, G_PARAM_READABLE));
397 
398 	/**
399 	 * TotemObject::file-opened:
400 	 * @totem: the #TotemObject which received the signal
401 	 * @mrl: the MRL of the opened stream
402 	 *
403 	 * The #TotemObject::file-opened signal is emitted when a new stream is opened by Totem.
404 	 */
405 	totem_table_signals[FILE_OPENED] =
406 		g_signal_new ("file-opened",
407 				G_TYPE_FROM_CLASS (object_class),
408 				G_SIGNAL_RUN_LAST,
409 				G_STRUCT_OFFSET (TotemObjectClass, file_opened),
410 				NULL, NULL,
411 				g_cclosure_marshal_VOID__STRING,
412 				G_TYPE_NONE, 1, G_TYPE_STRING);
413 
414 	/**
415 	 * TotemObject::file-has-played:
416 	 * @totem: the #TotemObject which received the signal
417 	 * @mrl: the MRL of the opened stream
418 	 *
419 	 * The #TotemObject::file-has-played signal is emitted when a new stream has started playing in Totem.
420 	 */
421 	totem_table_signals[FILE_HAS_PLAYED] =
422 		g_signal_new ("file-has-played",
423 				G_TYPE_FROM_CLASS (object_class),
424 				G_SIGNAL_RUN_LAST,
425 				G_STRUCT_OFFSET (TotemObjectClass, file_has_played),
426 				NULL, NULL,
427 				g_cclosure_marshal_VOID__STRING,
428 				G_TYPE_NONE, 1, G_TYPE_STRING);
429 
430 	/**
431 	 * TotemObject::file-closed:
432 	 * @totem: the #TotemObject which received the signal
433 	 *
434 	 * The #TotemObject::file-closed signal is emitted when Totem closes a stream.
435 	 */
436 	totem_table_signals[FILE_CLOSED] =
437 		g_signal_new ("file-closed",
438 				G_TYPE_FROM_CLASS (object_class),
439 				G_SIGNAL_RUN_LAST,
440 				G_STRUCT_OFFSET (TotemObjectClass, file_closed),
441 				NULL, NULL,
442 				g_cclosure_marshal_VOID__VOID,
443 				G_TYPE_NONE, 0, G_TYPE_NONE);
444 
445 	/**
446 	 * TotemObject::metadata-updated:
447 	 * @totem: the #TotemObject which received the signal
448 	 * @artist: the name of the artist, or %NULL
449 	 * @title: the stream title, or %NULL
450 	 * @album: the name of the stream's album, or %NULL
451 	 * @track_number: the stream's track number
452 	 *
453 	 * The #TotemObject::metadata-updated signal is emitted when the metadata of a stream is updated, typically
454 	 * when it's being loaded.
455 	 */
456 	totem_table_signals[METADATA_UPDATED] =
457 		g_signal_new ("metadata-updated",
458 				G_TYPE_FROM_CLASS (object_class),
459 				G_SIGNAL_RUN_LAST,
460 				G_STRUCT_OFFSET (TotemObjectClass, metadata_updated),
461 				NULL, NULL,
462 	                        g_cclosure_marshal_generic,
463 				G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
464 
465 	/**
466 	 * TotemObject::get-user-agent:
467 	 * @totem: the #TotemObject which received the signal
468 	 * @mrl: the MRL of the opened stream
469 	 *
470 	 * The #TotemObject::get-user-agent signal is emitted before opening a stream, so that plugins
471 	 * have the opportunity to return the user-agent to be set.
472 	 *
473 	 * Return value: allocated string representing the user-agent to use for @mrl
474 	 */
475 	totem_table_signals[GET_USER_AGENT] =
476 		g_signal_new ("get-user-agent",
477 			      G_TYPE_FROM_CLASS (object_class),
478 			      G_SIGNAL_RUN_LAST,
479 			      G_STRUCT_OFFSET (TotemObjectClass, get_user_agent),
480 			      accumulator_first_non_null_wins, NULL,
481 	                      g_cclosure_marshal_generic,
482 			      G_TYPE_STRING, 1, G_TYPE_STRING);
483 
484 	/**
485 	 * TotemObject::get-text-subtitle:
486 	 * @totem: the #TotemObject which received the signal
487 	 * @mrl: the MRL of the opened stream
488 	 *
489 	 * The #TotemObject::get-text-subtitle signal is emitted before opening a stream, so that plugins
490 	 * have the opportunity to detect or download text subtitles for the stream if necessary.
491 	 *
492 	 * Return value: allocated string representing the URI of the subtitle to use for @mrl
493 	 */
494 	totem_table_signals[GET_TEXT_SUBTITLE] =
495 		g_signal_new ("get-text-subtitle",
496 			      G_TYPE_FROM_CLASS (object_class),
497 			      G_SIGNAL_RUN_LAST,
498 			      G_STRUCT_OFFSET (TotemObjectClass, get_text_subtitle),
499 			      accumulator_first_non_null_wins, NULL,
500 	                      g_cclosure_marshal_generic,
501 			      G_TYPE_STRING, 1, G_TYPE_STRING);
502 }
503 
504 static void
totem_object_init(TotemObject * totem)505 totem_object_init (TotemObject *totem)
506 {
507 	GtkSettings *gtk_settings;
508 
509 	if (gtk_clutter_init (NULL, NULL) != CLUTTER_INIT_SUCCESS)
510 		g_warning ("gtk-clutter failed to initialise, expect problems from here on.");
511 
512 	gtk_settings = gtk_settings_get_default ();
513 	g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", TRUE, NULL);
514 
515 	totem->settings = g_settings_new (TOTEM_GSETTINGS_SCHEMA);
516 
517 	g_application_add_main_option_entries (G_APPLICATION (totem), all_options);
518 	g_application_add_option_group (G_APPLICATION (totem), bacon_video_widget_get_option_group ());
519 
520 	totem_app_actions_setup (totem);
521 }
522 
523 static void
totem_object_finalize(GObject * object)524 totem_object_finalize (GObject *object)
525 {
526 	TotemObject *totem = TOTEM_OBJECT (object);
527 
528 	g_clear_pointer (&totem->title, g_free);
529 	g_clear_pointer (&totem->subtitle, g_free);
530 	g_clear_pointer (&totem->search_string, g_free);
531 	g_clear_pointer (&totem->player_title, g_free);
532 	g_clear_object (&totem->custom_title);
533 
534 	G_OBJECT_CLASS (totem_object_parent_class)->finalize (object);
535 }
536 
537 static void
totem_object_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)538 totem_object_get_property (GObject *object,
539 			   guint property_id,
540 			   GValue *value,
541 			   GParamSpec *pspec)
542 {
543 	TotemObject *totem;
544 
545 	totem = TOTEM_OBJECT (object);
546 
547 	switch (property_id)
548 	{
549 	case PROP_FULLSCREEN:
550 		g_value_set_boolean (value, totem_object_is_fullscreen (totem));
551 		break;
552 	case PROP_PLAYING:
553 		g_value_set_boolean (value, totem_object_is_playing (totem));
554 		break;
555 	case PROP_STREAM_LENGTH:
556 		g_value_set_int64 (value, bacon_video_widget_get_stream_length (totem->bvw));
557 		break;
558 	case PROP_CURRENT_TIME:
559 		g_value_set_int64 (value, bacon_video_widget_get_current_time (totem->bvw));
560 		break;
561 	case PROP_SEEKABLE:
562 		g_value_set_boolean (value, totem_object_is_seekable (totem));
563 		break;
564 	case PROP_CURRENT_MRL:
565 		g_value_set_string (value, totem->mrl);
566 		break;
567 	case PROP_CURRENT_CONTENT_TYPE:
568 		g_value_take_string (value, totem_playlist_get_current_content_type (totem->playlist));
569 		break;
570 	case PROP_CURRENT_DISPLAY_NAME:
571 		g_value_take_string (value, totem_playlist_get_current_title (totem->playlist));
572 		break;
573 	case PROP_MAIN_PAGE:
574 		g_value_set_string (value, totem_object_get_main_page (totem));
575 		break;
576 	default:
577 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
578 	}
579 }
580 
581 /**
582  * totem_object_plugins_init:
583  * @totem: a #TotemObject
584  *
585  * Initialises the plugin engine and activates all the
586  * enabled plugins.
587  **/
588 void
totem_object_plugins_init(TotemObject * totem)589 totem_object_plugins_init (TotemObject *totem)
590 {
591 	if (totem->engine == NULL)
592 		totem->engine = totem_plugins_engine_get_default (totem);
593 }
594 
595 /**
596  * totem_object_plugins_shutdown:
597  * @totem: a #TotemObject
598  *
599  * Shuts down the plugin engine and deactivates all the
600  * plugins.
601  **/
602 void
totem_object_plugins_shutdown(TotemObject * totem)603 totem_object_plugins_shutdown (TotemObject *totem)
604 {
605 	if (totem->engine)
606 		totem_plugins_engine_shut_down (totem->engine);
607 	g_clear_object (&totem->engine);
608 }
609 
610 /**
611  * totem_object_get_main_window:
612  * @totem: a #TotemObject
613  *
614  * Gets Totem's main window and increments its reference count.
615  *
616  * Return value: (transfer full): Totem's main window
617  **/
618 GtkWindow *
totem_object_get_main_window(TotemObject * totem)619 totem_object_get_main_window (TotemObject *totem)
620 {
621 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), NULL);
622 
623 	g_object_ref (G_OBJECT (totem->win));
624 
625 	return GTK_WINDOW (totem->win);
626 }
627 
628 /**
629  * totem_object_get_menu_section:
630  * @totem: a #TotemObject
631  * @id: the ID for the menu section to look up
632  *
633  * Get the #GMenu of the given @id from the main Totem #GtkBuilder file.
634  *
635  * Return value: (transfer none) (nullable): a #GMenu or %NULL on failure
636  **/
637 GMenu *
totem_object_get_menu_section(TotemObject * totem,const char * id)638 totem_object_get_menu_section (TotemObject *totem,
639 			       const char  *id)
640 {
641 	GObject *object;
642 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), NULL);
643 
644 	object = gtk_builder_get_object (totem->xml, id);
645 	if (object == NULL || !G_IS_MENU (object))
646 		return NULL;
647 
648 	return G_MENU (object);
649 }
650 
651 /**
652  * totem_object_empty_menu_section:
653  * @totem: a #TotemObject
654  * @id: the ID for the menu section to empty
655  *
656  * Empty the GMenu section pointed to by @id, and remove any
657  * related actions. Note that menu items with specific target
658  * will not have the associated action removed.
659  **/
660 void
totem_object_empty_menu_section(TotemObject * totem,const char * id)661 totem_object_empty_menu_section (TotemObject *totem,
662 				 const char  *id)
663 {
664 	GMenu *menu;
665 
666 	g_return_if_fail (TOTEM_IS_OBJECT (totem));
667 
668 	menu = G_MENU (gtk_builder_get_object (totem->xml, id));
669 	g_return_if_fail (menu != NULL);
670 
671 	while (g_menu_model_get_n_items (G_MENU_MODEL (menu)) > 0) {
672 		const char *action;
673 		g_menu_model_get_item_attribute (G_MENU_MODEL (menu), 0, G_MENU_ATTRIBUTE_ACTION, "s", &action);
674 		if (g_str_has_prefix (action, "app.")) {
675 			GVariant *target;
676 
677 			target = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, G_MENU_ATTRIBUTE_TARGET, NULL);
678 
679 			/* Don't remove actions that have a specific target */
680 			if (target == NULL)
681 				g_action_map_remove_action (G_ACTION_MAP (totem), action + strlen ("app."));
682 			else
683 				g_variant_unref (target);
684 		}
685 		g_menu_remove (G_MENU (menu), 0);
686 	}
687 }
688 
689 /**
690  * totem_object_get_video_widget:
691  * @totem: a #TotemObject
692  *
693  * Gets Totem's video widget and increments its reference count.
694  *
695  * Return value: (transfer full): Totem's video widget
696  **/
697 GtkWidget *
totem_object_get_video_widget(TotemObject * totem)698 totem_object_get_video_widget (TotemObject *totem)
699 {
700 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), NULL);
701 
702 	g_object_ref (G_OBJECT (totem->bvw));
703 
704 	return GTK_WIDGET (totem->bvw);
705 }
706 
707 /**
708  * totem_object_get_current_time:
709  * @totem: a #TotemObject
710  *
711  * Gets the current position's time in the stream as a gint64.
712  *
713  * Return value: the current position in the stream
714  **/
715 gint64
totem_object_get_current_time(TotemObject * totem)716 totem_object_get_current_time (TotemObject *totem)
717 {
718 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), 0);
719 
720 	return bacon_video_widget_get_current_time (totem->bvw);
721 }
722 
723 static void
add_items_to_playlist_and_play_cb(TotemPlaylist * playlist,GAsyncResult * async_result,TotemObject * totem)724 add_items_to_playlist_and_play_cb (TotemPlaylist *playlist, GAsyncResult *async_result, TotemObject *totem)
725 {
726 	char *mrl, *subtitle;
727 
728 	/* totem_playlist_add_mrls_finish() never returns an error */
729 	totem_playlist_add_mrls_finish (playlist, async_result, NULL);
730 
731 	totem_signal_unblock_by_data (playlist, totem);
732 
733 	/* And start playback */
734 	mrl = totem_playlist_get_current_mrl (playlist, &subtitle);
735 	totem_object_set_mrl_and_play (totem, mrl, subtitle);
736 	g_free (mrl);
737 	g_free (subtitle);
738 }
739 
740 typedef struct {
741 	TotemObject *totem;
742 	gchar *uri;
743 	gchar *display_name;
744 	gboolean play;
745 } AddToPlaylistData;
746 
747 static void
add_to_playlist_and_play_cb(TotemPlaylist * playlist,GAsyncResult * async_result,AddToPlaylistData * data)748 add_to_playlist_and_play_cb (TotemPlaylist *playlist, GAsyncResult *async_result, AddToPlaylistData *data)
749 {
750 	int end = -1;
751 	gboolean playlist_changed;
752 	GError *error = NULL;
753 
754 	playlist_changed = totem_playlist_add_mrl_finish (playlist, async_result, &error);
755 
756 	if (playlist_changed == FALSE && error != NULL) {
757 		/* FIXME: Crappy dialogue */
758 		totem_object_show_error (data->totem, "", error->message);
759 		g_error_free (error);
760 	}
761 
762 	if (data->play)
763 		end = totem_playlist_get_last (playlist);
764 
765 	totem_signal_unblock_by_data (playlist, data->totem);
766 
767 	if (data->play && playlist_changed && end != -1) {
768 		char *mrl, *subtitle;
769 
770 		subtitle = NULL;
771 		totem_playlist_set_current (playlist, end);
772 		mrl = totem_playlist_get_current_mrl (playlist, &subtitle);
773 		totem_object_set_mrl_and_play (data->totem, mrl, subtitle);
774 		g_free (mrl);
775 		g_free (subtitle);
776 	}
777 
778 	/* Free the closure data */
779 	g_object_unref (data->totem);
780 	g_free (data->uri);
781 	g_free (data->display_name);
782 	g_slice_free (AddToPlaylistData, data);
783 }
784 
785 static gboolean
save_session_timeout_cb(Totem * totem)786 save_session_timeout_cb (Totem *totem)
787 {
788 	totem_session_save (totem);
789 	return TRUE;
790 }
791 
792 static void
setup_save_timeout_cb(Totem * totem,gboolean enable)793 setup_save_timeout_cb (Totem    *totem,
794 		       gboolean  enable)
795 {
796 	if (enable && totem->save_timeout_id == 0) {
797 		totem->save_timeout_id = g_timeout_add_seconds (TOTEM_SESSION_SAVE_TIMEOUT,
798 								(GSourceFunc) save_session_timeout_cb,
799 								totem);
800 		g_source_set_name_by_id (totem->save_timeout_id, "[totem] save_session_timeout_cb");
801 	} else if (totem->save_timeout_id > 0) {
802 		g_source_remove (totem->save_timeout_id);
803 		totem->save_timeout_id = 0;
804 	}
805 }
806 
807 /**
808  * totem_object_add_to_playlist:
809  * @totem: a #TotemObject
810  * @uri: the URI to add to the playlist
811  * @display_name: (allow-none): the display name of the URI
812  * @play: whether to play the added item
813  *
814  * Add @uri to the playlist and play it immediately.
815  **/
816 void
totem_object_add_to_playlist(TotemObject * totem,const char * uri,const char * display_name,gboolean play)817 totem_object_add_to_playlist (TotemObject *totem,
818 			      const char  *uri,
819 			      const char  *display_name,
820 			      gboolean     play)
821 {
822 	AddToPlaylistData *data;
823 
824 	/* Block all signals from the playlist until we're finished. They're unblocked in the callback, add_to_playlist_and_play_cb.
825 	 * There are no concurrency issues here, since blocking the signals multiple times should require them to be unblocked the
826 	 * same number of times before they fire again. */
827 	totem_signal_block_by_data (totem->playlist, totem);
828 
829 	data = g_slice_new (AddToPlaylistData);
830 	data->totem = g_object_ref (totem);
831 	data->uri = g_strdup (uri);
832 	data->display_name = g_strdup (display_name);
833 	data->play = play;
834 
835 	totem_playlist_add_mrl (totem->playlist, uri, display_name, TRUE,
836 	                        NULL, (GAsyncReadyCallback) add_to_playlist_and_play_cb, data);
837 }
838 
839 /**
840  * totem_object_add_items_to_playlist:
841  * @totem: a #TotemObject
842  * @items: a #GList of #TotemPlaylistMrlData
843  *
844  * Add @items to the playlist and play them immediately.
845  * This function takes ownership of both the list and its elements when
846  * called, so don't free either after calling
847  * totem_object_add_items_to_playlist().
848  **/
849 void
totem_object_add_items_to_playlist(TotemObject * totem,GList * items)850 totem_object_add_items_to_playlist (TotemObject *totem,
851 				    GList       *items)
852 {
853 	/* Block all signals from the playlist until we're finished. They're unblocked in the callback, add_to_playlist_and_play_cb.
854 	 * There are no concurrency issues here, since blocking the signals multiple times should require them to be unblocked the
855 	 * same number of times before they fire again. */
856 	totem_signal_block_by_data (totem->playlist, totem);
857 
858 	totem_playlist_add_mrls (totem->playlist, items, TRUE, NULL,
859 				 (GAsyncReadyCallback) add_items_to_playlist_and_play_cb, totem);
860 }
861 
862 /**
863  * totem_object_clear_playlist:
864  * @totem: a #TotemObject
865  *
866  * Empties the current playlist.
867  **/
868 void
totem_object_clear_playlist(TotemObject * totem)869 totem_object_clear_playlist (TotemObject *totem)
870 {
871 	totem_playlist_clear (totem->playlist);
872 }
873 
874 /**
875  * totem_object_get_current_mrl:
876  * @totem: a #TotemObject
877  *
878  * Get the MRL of the current stream, or %NULL if nothing's playing.
879  * Free with g_free().
880  *
881  * Return value: a newly-allocated string containing the MRL of the current stream
882  **/
883 char *
totem_object_get_current_mrl(TotemObject * totem)884 totem_object_get_current_mrl (TotemObject *totem)
885 {
886 	return totem_playlist_get_current_mrl (totem->playlist, NULL);
887 }
888 
889 /**
890  * totem_object_get_playlist_length:
891  * @totem: a #TotemObject
892  *
893  * Returns the length of the current playlist.
894  *
895  * Return value: the playlist length
896  **/
897 guint
totem_object_get_playlist_length(TotemObject * totem)898 totem_object_get_playlist_length (TotemObject *totem)
899 {
900 	int last;
901 
902 	last = totem_playlist_get_last (totem->playlist);
903 	if (last == -1)
904 		return 0;
905 	return last + 1;
906 }
907 
908 /**
909  * totem_object_get_playlist_pos:
910  * @totem: a #TotemObject
911  *
912  * Returns the <code class="literal">0</code>-based index of the current entry in the playlist. If
913  * there is no current entry in the playlist, <code class="literal">-1</code> is returned.
914  *
915  * Return value: the index of the current playlist entry, or <code class="literal">-1</code>
916  **/
917 int
totem_object_get_playlist_pos(TotemObject * totem)918 totem_object_get_playlist_pos (TotemObject *totem)
919 {
920 	return totem_playlist_get_current (totem->playlist);
921 }
922 
923 /**
924  * totem_object_get_title_at_playlist_pos:
925  * @totem: a #TotemObject
926  * @playlist_index: the <code class="literal">0</code>-based entry index
927  *
928  * Gets the title of the playlist entry at @index.
929  *
930  * Return value: the entry title at @index, or %NULL; free with g_free()
931  **/
932 char *
totem_object_get_title_at_playlist_pos(TotemObject * totem,guint playlist_index)933 totem_object_get_title_at_playlist_pos (TotemObject *totem, guint playlist_index)
934 {
935 	return totem_playlist_get_title (totem->playlist, playlist_index);
936 }
937 
938 /**
939  * totem_object_get_short_title:
940  * @totem: a #TotemObject
941  *
942  * Gets the title of the current entry in the playlist.
943  *
944  * Return value: the current entry's title, or %NULL; free with g_free()
945  **/
946 char *
totem_object_get_short_title(TotemObject * totem)947 totem_object_get_short_title (TotemObject *totem)
948 {
949 	return totem_playlist_get_current_title (totem->playlist);
950 }
951 
952 /**
953  * totem_object_add_to_view:
954  * @totem: a #TotemObject
955  * @file: a #GFile representing a media
956  * @title: a title for the media, or %NULL
957  *
958  * Adds a local media file to the main view.
959  *
960  **/
961 void
totem_object_add_to_view(TotemObject * totem,GFile * file,const char * title)962 totem_object_add_to_view (TotemObject *totem,
963 			  GFile       *file,
964 			  const char  *title)
965 {
966 	char *uri;
967 
968 	uri = g_file_get_uri (file);
969 	if (!totem_grilo_add_item_to_recent (TOTEM_GRILO (totem->grilo),
970 					     uri, title, FALSE)) {
971 		g_warning ("Failed to add '%s' to view", uri);
972 	}
973 	g_free (uri);
974 }
975 
976 /**
977  * totem_object_set_current_subtitle:
978  * @totem: a #TotemObject
979  * @subtitle_uri: the URI of the subtitle file to add
980  *
981  * Add the @subtitle_uri subtitle file to the playlist, setting it as the subtitle for the current
982  * playlist entry.
983  **/
984 void
totem_object_set_current_subtitle(TotemObject * totem,const char * subtitle_uri)985 totem_object_set_current_subtitle (TotemObject *totem, const char *subtitle_uri)
986 {
987 	totem_playlist_set_current_subtitle (totem->playlist, subtitle_uri);
988 }
989 
990 void
totem_object_set_main_page(TotemObject * totem,const char * page_id)991 totem_object_set_main_page (TotemObject *totem,
992 			    const char  *page_id)
993 {
994 	if (g_strcmp0 (page_id, gtk_stack_get_visible_child_name (GTK_STACK (totem->stack))) == 0) {
995 		if (g_strcmp0 (page_id, "grilo") == 0)
996 			totem_grilo_start (TOTEM_GRILO (totem->grilo));
997 		else
998 			totem_grilo_pause (TOTEM_GRILO (totem->grilo));
999 		return;
1000 	}
1001 
1002 	gtk_stack_set_visible_child_full (GTK_STACK (totem->stack), page_id, GTK_STACK_TRANSITION_TYPE_NONE);
1003 
1004 	if (g_strcmp0 (page_id, "player") == 0) {
1005 		totem_grilo_pause (TOTEM_GRILO (totem->grilo));
1006 		g_object_get (totem->header,
1007 			      "title", &totem->title,
1008 			      "subtitle", &totem->subtitle,
1009 			      "search-string", &totem->search_string,
1010 			      "select-mode", &totem->select_mode,
1011 			      "custom-title", &totem->custom_title,
1012 			      NULL);
1013 		g_object_set (totem->header,
1014 			      "show-back-button", TRUE,
1015 			      "show-select-button", FALSE,
1016 			      "show-search-button", FALSE,
1017 			      "title", totem->player_title,
1018 			      "subtitle", NULL,
1019 			      "search-string", NULL,
1020 			      "select-mode", FALSE,
1021 			      "custom-title", NULL,
1022 			      NULL);
1023 		gtk_widget_show (totem->fullscreen_button);
1024 		gtk_widget_show (totem->gear_button);
1025 		gtk_widget_hide (totem->add_button);
1026 		gtk_widget_hide (totem->main_menu_button);
1027 		bacon_video_widget_show_popup (totem->bvw);
1028 	} else if (g_strcmp0 (page_id, "grilo") == 0) {
1029 		totem_grilo_start (TOTEM_GRILO (totem->grilo));
1030 		g_object_set (totem->header,
1031 			      "show-back-button", totem_grilo_get_show_back_button (TOTEM_GRILO (totem->grilo)),
1032 			      "show-select-button", TRUE,
1033 			      "show-search-button", TRUE,
1034 			      "title", totem->title,
1035 			      "subtitle", totem->subtitle,
1036 			      "search-string", totem->search_string,
1037 			      "select-mode", totem->select_mode,
1038 			      "custom-title", totem->custom_title,
1039 			      NULL);
1040 		g_clear_pointer (&totem->title, g_free);
1041 		g_clear_pointer (&totem->subtitle, g_free);
1042 		g_clear_pointer (&totem->search_string, g_free);
1043 		g_clear_pointer (&totem->player_title, g_free);
1044 		g_clear_object (&totem->custom_title);
1045 		gtk_widget_show (totem->main_menu_button);
1046 		gtk_widget_hide (totem->fullscreen_button);
1047 		gtk_widget_hide (totem->gear_button);
1048 		if (totem_grilo_get_current_page (TOTEM_GRILO (totem->grilo)) == TOTEM_GRILO_PAGE_RECENT)
1049 			gtk_widget_show (totem->add_button);
1050 		totem_grilo_start (TOTEM_GRILO (totem->grilo));
1051 	}
1052 
1053 	g_object_notify (G_OBJECT (totem), "main-page");
1054 }
1055 
1056 /**
1057  * totem_object_get_main_page:
1058  * @totem: a #TotemObject
1059  *
1060  * Gets the identifier for the current page in Totem's
1061  * main view.
1062  *
1063  * Return value: identifier for current page
1064  */
1065 const char *
totem_object_get_main_page(Totem * totem)1066 totem_object_get_main_page (Totem *totem)
1067 {
1068 	return gtk_stack_get_visible_child_name (GTK_STACK (totem->stack));
1069 }
1070 
1071 /*
1072  * emit_file_opened:
1073  * @totem: a #TotemObject
1074  * @mrl: the MRL opened
1075  *
1076  * Emits the #TotemObject::file-opened signal on @totem, with the
1077  * specified @mrl.
1078  **/
1079 static void
emit_file_opened(TotemObject * totem,const char * mrl)1080 emit_file_opened (TotemObject *totem,
1081 		   const char *mrl)
1082 {
1083 	totem_session_save (totem);
1084 	setup_save_timeout_cb (totem, TRUE);
1085 	g_signal_emit (G_OBJECT (totem),
1086 		       totem_table_signals[FILE_OPENED],
1087 		       0, mrl);
1088 }
1089 
1090 /*
1091  * emit_file_closed:
1092  * @totem: a #TotemObject
1093  *
1094  * Emits the #TotemObject::file-closed signal on @totem.
1095  **/
1096 static void
emit_file_closed(TotemObject * totem)1097 emit_file_closed (TotemObject *totem)
1098 {
1099 	setup_save_timeout_cb (totem, FALSE);
1100 	totem_session_save (totem);
1101 	g_signal_emit (G_OBJECT (totem),
1102 		       totem_table_signals[FILE_CLOSED],
1103 		       0);
1104 }
1105 
1106 /**
1107  * totem_file_has_played:
1108  * @totem: a #TotemObject
1109  *
1110  * Emits the #TotemObject::file-played signal on @totem.
1111  **/
1112 void
totem_file_has_played(TotemObject * totem,const char * mrl)1113 totem_file_has_played (TotemObject *totem,
1114 		       const char  *mrl)
1115 {
1116 	g_signal_emit (G_OBJECT (totem),
1117 		       totem_table_signals[FILE_HAS_PLAYED],
1118 		       0, mrl);
1119 }
1120 
1121 /*
1122  * emit_metadata_updated:
1123  * @totem: a #TotemObject
1124  * @artist: the stream's artist, or %NULL
1125  * @title: the stream's title, or %NULL
1126  * @album: the stream's album, or %NULL
1127  * @track_num: the track number of the stream
1128  *
1129  * Emits the #TotemObject::metadata-updated signal on @totem,
1130  * with the specified stream data.
1131  **/
1132 static void
emit_metadata_updated(TotemObject * totem,const char * artist,const char * title,const char * album,guint track_num)1133 emit_metadata_updated (TotemObject *totem,
1134 			const char *artist,
1135 			const char *title,
1136 			const char *album,
1137 			guint track_num)
1138 {
1139 	g_signal_emit (G_OBJECT (totem),
1140 		       totem_table_signals[METADATA_UPDATED],
1141 		       0,
1142 		       artist,
1143 		       title,
1144 		       album,
1145 		       track_num);
1146 }
1147 
1148 GQuark
totem_remote_command_quark(void)1149 totem_remote_command_quark (void)
1150 {
1151 	static GQuark quark = 0;
1152 	if (!quark)
1153 		quark = g_quark_from_static_string ("totem_remote_command");
1154 
1155 	return quark;
1156 }
1157 
1158 /* This should really be standard. */
1159 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1160 
1161 GType
totem_remote_command_get_type(void)1162 totem_remote_command_get_type (void)
1163 {
1164 	static GType etype = 0;
1165 
1166 	if (etype == 0) {
1167 		static const GEnumValue values[] = {
1168 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_UNKNOWN, "unknown"),
1169 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_PLAY, "play"),
1170 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_PAUSE, "pause"),
1171 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_STOP, "stop"),
1172 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_PLAYPAUSE, "play-pause"),
1173 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_NEXT, "next"),
1174 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_PREVIOUS, "previous"),
1175 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_SEEK_FORWARD, "seek-forward"),
1176 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_SEEK_BACKWARD, "seek-backward"),
1177 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_VOLUME_UP, "volume-up"),
1178 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_VOLUME_DOWN, "volume-down"),
1179 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_FULLSCREEN, "fullscreen"),
1180 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_QUIT, "quit"),
1181 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_ENQUEUE, "enqueue"),
1182 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_REPLACE, "replace"),
1183 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_SHOW, "show"),
1184 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_UP, "up"),
1185 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_DOWN, "down"),
1186 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_LEFT, "left"),
1187 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_RIGHT, "right"),
1188 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_SELECT, "select"),
1189 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_DVD_MENU, "dvd-menu"),
1190 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_ZOOM_UP, "zoom-up"),
1191 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_ZOOM_DOWN, "zoom-down"),
1192 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_EJECT, "eject"),
1193 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_PLAY_DVD, "play-dvd"),
1194 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_MUTE, "mute"),
1195 			ENUM_ENTRY (TOTEM_REMOTE_COMMAND_TOGGLE_ASPECT, "toggle-aspect-ratio"),
1196 			{ 0, NULL, NULL }
1197 		};
1198 
1199 		etype = g_enum_register_static ("TotemRemoteCommand", values);
1200 	}
1201 
1202 	return etype;
1203 }
1204 
1205 GQuark
totem_remote_setting_quark(void)1206 totem_remote_setting_quark (void)
1207 {
1208 	static GQuark quark = 0;
1209 	if (!quark)
1210 		quark = g_quark_from_static_string ("totem_remote_setting");
1211 
1212 	return quark;
1213 }
1214 
1215 GType
totem_remote_setting_get_type(void)1216 totem_remote_setting_get_type (void)
1217 {
1218 	static GType etype = 0;
1219 
1220 	if (etype == 0) {
1221 		static const GEnumValue values[] = {
1222 			ENUM_ENTRY (TOTEM_REMOTE_SETTING_REPEAT, "repeat"),
1223 			{ 0, NULL, NULL }
1224 		};
1225 
1226 		etype = g_enum_register_static ("TotemRemoteSetting", values);
1227 	}
1228 
1229 	return etype;
1230 }
1231 
1232 static void
reset_seek_status(TotemObject * totem)1233 reset_seek_status (TotemObject *totem)
1234 {
1235 	/* Release the lock and reset everything so that we
1236 	 * avoid being "stuck" seeking on errors */
1237 
1238 	if (totem->seek_lock != FALSE) {
1239 		totem->seek_lock = FALSE;
1240 		bacon_video_widget_unmark_popup_busy (totem->bvw, "seek started");
1241 		bacon_video_widget_seek (totem->bvw, 0, NULL);
1242 		bacon_video_widget_stop (totem->bvw);
1243 		play_pause_set_label (totem, STATE_STOPPED);
1244 	}
1245 }
1246 
1247 /**
1248  * totem_object_show_error:
1249  * @totem: a #TotemObject
1250  * @title: the error dialog title
1251  * @reason: the error dialog text
1252  *
1253  * Displays a non-blocking error dialog with the
1254  * given @title and @reason.
1255  **/
1256 void
totem_object_show_error(TotemObject * totem,const char * title,const char * reason)1257 totem_object_show_error (TotemObject *totem, const char *title, const char *reason)
1258 {
1259 	reset_seek_status (totem);
1260 	totem_interface_error (title, reason,
1261 			GTK_WINDOW (totem->win));
1262 }
1263 
1264 G_GNUC_NORETURN void
totem_object_show_error_and_exit(const char * title,const char * reason,TotemObject * totem)1265 totem_object_show_error_and_exit (const char *title,
1266 		const char *reason, TotemObject *totem)
1267 {
1268 	reset_seek_status (totem);
1269 	totem_interface_error_blocking (title, reason,
1270 			GTK_WINDOW (totem->win));
1271 	totem_object_exit (totem);
1272 }
1273 
1274 static void
totem_object_save_size(TotemObject * totem)1275 totem_object_save_size (TotemObject *totem)
1276 {
1277 	if (totem->bvw == NULL)
1278 		return;
1279 
1280 	if (totem_object_is_fullscreen (totem) != FALSE)
1281 		return;
1282 
1283 	/* Save the size of the video widget */
1284 	gtk_window_get_size (GTK_WINDOW (totem->win), &totem->window_w, &totem->window_h);
1285 }
1286 
1287 static void
totem_object_save_state(TotemObject * totem)1288 totem_object_save_state (TotemObject *totem)
1289 {
1290 	GKeyFile *keyfile;
1291 	char *contents, *filename;
1292 
1293 	if (totem->win == NULL)
1294 		return;
1295 	if (totem->window_w == 0
1296 	    || totem->window_h == 0)
1297 		return;
1298 
1299 	keyfile = g_key_file_new ();
1300 	g_key_file_set_integer (keyfile, "State",
1301 				"window_w", totem->window_w);
1302 	g_key_file_set_integer (keyfile, "State",
1303 			"window_h", totem->window_h);
1304 	g_key_file_set_boolean (keyfile, "State",
1305 			"maximised", totem->maximised);
1306 
1307 	contents = g_key_file_to_data (keyfile, NULL, NULL);
1308 	g_key_file_free (keyfile);
1309 	filename = g_build_filename (totem_dot_dir (), "state.ini", NULL);
1310 	g_file_set_contents (filename, contents, -1, NULL);
1311 
1312 	g_free (filename);
1313 	g_free (contents);
1314 }
1315 
1316 /**
1317  * totem_object_exit:
1318  * @totem: a #TotemObject
1319  *
1320  * Closes Totem.
1321  **/
1322 void
totem_object_exit(TotemObject * totem)1323 totem_object_exit (TotemObject *totem)
1324 {
1325 	GdkDisplay *display = NULL;
1326 
1327 	/* Shut down the plugins first, allowing them to display modal dialogues (etc.) without threat of being killed from another thread */
1328 	if (totem != NULL && totem->engine != NULL)
1329 		totem_object_plugins_shutdown (totem);
1330 
1331 	if (gtk_main_level () > 0)
1332 		gtk_main_quit ();
1333 
1334 	if (totem == NULL)
1335 		exit (0);
1336 
1337 	if (totem->bvw)
1338 		totem_object_save_size (totem);
1339 
1340 	if (totem->win != NULL) {
1341 		gtk_widget_hide (totem->win);
1342 		display = gtk_widget_get_display (totem->win);
1343 	}
1344 
1345 	if (totem->prefs != NULL)
1346 		gtk_widget_hide (totem->prefs);
1347 
1348 	if (display != NULL)
1349 		gdk_display_sync (display);
1350 
1351 	setup_save_timeout_cb (totem, FALSE);
1352 	totem_session_cleanup (totem);
1353 
1354 	if (totem->bvw)
1355 		bacon_video_widget_close (totem->bvw);
1356 
1357 	totem_object_save_state (totem);
1358 
1359 	totem_sublang_exit (totem);
1360 	totem_destroy_file_filters ();
1361 
1362 	g_clear_object (&totem->settings);
1363 
1364 	if (totem->win)
1365 		gtk_widget_destroy (GTK_WIDGET (totem->win));
1366 
1367 	g_object_unref (totem);
1368 
1369 	exit (0);
1370 }
1371 
1372 G_GNUC_NORETURN gboolean
main_window_destroy_cb(GtkWidget * widget,GdkEvent * event,TotemObject * totem)1373 main_window_destroy_cb (GtkWidget *widget, GdkEvent *event, TotemObject *totem)
1374 {
1375 	totem_object_exit (totem);
1376 }
1377 
1378 static void
play_pause_set_label(TotemObject * totem,TotemStates state)1379 play_pause_set_label (TotemObject *totem, TotemStates state)
1380 {
1381 	GtkWidget *image;
1382 	const char *id, *tip;
1383 
1384 	if (state == totem->state)
1385 		return;
1386 
1387 	switch (state)
1388 	{
1389 	case STATE_PLAYING:
1390 		id = "media-playback-pause-symbolic";
1391 		tip = N_("Pause");
1392 		totem_playlist_set_playing (totem->playlist, TOTEM_PLAYLIST_STATUS_PLAYING);
1393 		break;
1394 	case STATE_PAUSED:
1395 		id = "media-playback-start-symbolic";
1396 		tip = N_("Play");
1397 		totem_playlist_set_playing (totem->playlist, TOTEM_PLAYLIST_STATUS_PAUSED);
1398 		break;
1399 	case STATE_STOPPED:
1400 		bacon_time_label_set_time (totem->time_label,
1401 					   0, 0);
1402 		bacon_time_label_set_time (totem->time_rem_label,
1403 					   0, 0);
1404 		id = "media-playback-start-symbolic";
1405 		totem_playlist_set_playing (totem->playlist, TOTEM_PLAYLIST_STATUS_NONE);
1406 		tip = N_("Play");
1407 		break;
1408 	default:
1409 		g_assert_not_reached ();
1410 		return;
1411 	}
1412 
1413 	gtk_widget_set_tooltip_text (totem->play_button, _(tip));
1414 	image = gtk_button_get_image (GTK_BUTTON (totem->play_button));
1415 	gtk_image_set_from_icon_name (GTK_IMAGE (image), id, GTK_ICON_SIZE_MENU);
1416 
1417 	totem->state = state;
1418 
1419 	g_object_notify (G_OBJECT (totem), "playing");
1420 }
1421 
1422 void
totem_object_eject(TotemObject * totem)1423 totem_object_eject (TotemObject *totem)
1424 {
1425 	GMount *mount;
1426 
1427 	mount = totem_get_mount_for_media (totem->mrl);
1428 	if (mount == NULL)
1429 		return;
1430 
1431 	g_clear_pointer (&totem->mrl, g_free);
1432 	bacon_video_widget_close (totem->bvw);
1433 	emit_file_closed (totem);
1434 	totem->has_played_emitted = FALSE;
1435 
1436 	/* The volume monitoring will take care of removing the items */
1437 	g_mount_eject_with_operation (mount, G_MOUNT_UNMOUNT_NONE, NULL, NULL, NULL, NULL);
1438 	g_object_unref (mount);
1439 }
1440 
1441 /**
1442  * totem_object_play:
1443  * @totem: a #TotemObject
1444  *
1445  * Plays the current stream. If Totem is already playing, it continues
1446  * to play. If the stream cannot be played, and error dialog is displayed.
1447  **/
1448 void
totem_object_play(TotemObject * totem)1449 totem_object_play (TotemObject *totem)
1450 {
1451 	GError *err = NULL;
1452 	int retval;
1453 	char *msg, *disp;
1454 
1455 	if (totem->mrl == NULL)
1456 		return;
1457 
1458 	if (bacon_video_widget_is_playing (totem->bvw) != FALSE)
1459 		return;
1460 
1461 	retval = bacon_video_widget_play (totem->bvw,  &err);
1462 	play_pause_set_label (totem, retval ? STATE_PLAYING : STATE_STOPPED);
1463 
1464 	if (retval != FALSE) {
1465 		if (totem->has_played_emitted == FALSE) {
1466 			totem_file_has_played (totem, totem->mrl);
1467 			totem->has_played_emitted = TRUE;
1468 		}
1469 		return;
1470 	}
1471 
1472 	disp = totem_uri_escape_for_display (totem->mrl);
1473 	msg = g_strdup_printf(_("Totem could not play “%s”."), disp);
1474 	g_free (disp);
1475 
1476 	totem_object_show_error (totem, msg, err->message);
1477 	bacon_video_widget_stop (totem->bvw);
1478 	play_pause_set_label (totem, STATE_STOPPED);
1479 	g_free (msg);
1480 	g_error_free (err);
1481 }
1482 
1483 static void
totem_object_seek(TotemObject * totem,double pos)1484 totem_object_seek (TotemObject *totem, double pos)
1485 {
1486 	GError *err = NULL;
1487 	int retval;
1488 
1489 	if (totem->mrl == NULL)
1490 		return;
1491 	if (bacon_video_widget_is_seekable (totem->bvw) == FALSE)
1492 		return;
1493 
1494 	retval = bacon_video_widget_seek (totem->bvw, pos, &err);
1495 
1496 	if (retval == FALSE)
1497 	{
1498 		char *msg, *disp;
1499 
1500 		disp = totem_uri_escape_for_display (totem->mrl);
1501 		msg = g_strdup_printf(_("Totem could not play “%s”."), disp);
1502 		g_free (disp);
1503 
1504 		reset_seek_status (totem);
1505 
1506 		totem_object_show_error (totem, msg, err->message);
1507 		g_free (msg);
1508 		g_error_free (err);
1509 	}
1510 }
1511 
1512 static void
totem_object_set_mrl_and_play(TotemObject * totem,const char * mrl,const char * subtitle)1513 totem_object_set_mrl_and_play (TotemObject *totem, const char *mrl, const char *subtitle)
1514 {
1515 	totem_object_set_mrl (totem, mrl, subtitle);
1516 	totem_object_play (totem);
1517 }
1518 
1519 static gboolean
totem_object_open_dialog(TotemObject * totem,const char * path)1520 totem_object_open_dialog (TotemObject *totem, const char *path)
1521 {
1522 	GSList *filenames, *l;
1523 
1524 	filenames = totem_add_files (GTK_WINDOW (totem->win), path);
1525 
1526 	if (filenames == NULL)
1527 		return FALSE;
1528 
1529 	for (l = filenames; l != NULL; l = l->next) {
1530 		char *uri = l->data;
1531 
1532 		totem_grilo_add_item_to_recent (TOTEM_GRILO (totem->grilo), uri, NULL, FALSE);
1533 		g_free (uri);
1534 	}
1535 	g_slist_free (filenames);
1536 
1537 	return TRUE;
1538 }
1539 
1540 /**
1541  * totem_object_play_pause:
1542  * @totem: a #TotemObject
1543  *
1544  * Gets the current MRL from the playlist and attempts to play it.
1545  * If the stream is already playing, playback is paused.
1546  **/
1547 void
totem_object_play_pause(TotemObject * totem)1548 totem_object_play_pause (TotemObject *totem)
1549 {
1550 	if (totem->mrl == NULL) {
1551 		char *mrl, *subtitle;
1552 
1553 		/* Try to pull an mrl from the playlist */
1554 		mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
1555 		if (mrl == NULL) {
1556 			play_pause_set_label (totem, STATE_STOPPED);
1557 			return;
1558 		} else {
1559 			totem_object_set_mrl_and_play (totem, mrl, subtitle);
1560 			g_free (mrl);
1561 			g_free (subtitle);
1562 			return;
1563 		}
1564 	}
1565 
1566 	if (bacon_video_widget_is_playing (totem->bvw) == FALSE) {
1567 		if (bacon_video_widget_play (totem->bvw, NULL) != FALSE &&
1568 		    totem->has_played_emitted == FALSE) {
1569 			totem_file_has_played (totem, totem->mrl);
1570 			totem->has_played_emitted = TRUE;
1571 		}
1572 		play_pause_set_label (totem, STATE_PLAYING);
1573 	} else {
1574 		bacon_video_widget_pause (totem->bvw);
1575 		play_pause_set_label (totem, STATE_PAUSED);
1576 	}
1577 }
1578 
1579 /**
1580  * totem_object_stop:
1581  * @totem: a #TotemObject
1582  *
1583  * Stops playback, and sets the playlist back at the start.
1584  */
1585 void
totem_object_stop(TotemObject * totem)1586 totem_object_stop (TotemObject *totem)
1587 {
1588 	char *mrl, *subtitle;
1589 
1590 	totem_playlist_set_at_start (totem->playlist);
1591 	update_buttons (totem);
1592 	bacon_video_widget_stop (totem->bvw);
1593 	play_pause_set_label (totem, STATE_STOPPED);
1594 	mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
1595 	if (mrl != NULL) {
1596 		totem_object_set_mrl (totem, mrl, subtitle);
1597 		bacon_video_widget_pause (totem->bvw);
1598 		g_free (mrl);
1599 		g_free (subtitle);
1600 	}
1601 }
1602 
1603 /**
1604  * totem_object_pause:
1605  * @totem: a #TotemObject
1606  *
1607  * Pauses the current stream. If Totem is already paused, it continues
1608  * to be paused.
1609  **/
1610 void
totem_object_pause(TotemObject * totem)1611 totem_object_pause (TotemObject *totem)
1612 {
1613 	if (bacon_video_widget_is_playing (totem->bvw) != FALSE) {
1614 		bacon_video_widget_pause (totem->bvw);
1615 		play_pause_set_label (totem, STATE_PAUSED);
1616 	}
1617 }
1618 
1619 gboolean
window_state_event_cb(GtkWidget * window,GdkEventWindowState * event,TotemObject * totem)1620 window_state_event_cb (GtkWidget           *window,
1621 		       GdkEventWindowState *event,
1622 		       TotemObject         *totem)
1623 {
1624 	GAction *action;
1625 
1626 	totem->maximised = !!(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED);
1627 
1628 	if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) == 0)
1629 		return FALSE;
1630 
1631 	if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
1632 		if (totem->controls_visibility != TOTEM_CONTROLS_UNDEFINED)
1633 			totem_object_save_size (totem);
1634 
1635 		totem->controls_visibility = TOTEM_CONTROLS_FULLSCREEN;
1636 		show_controls (totem, FALSE);
1637 	} else {
1638 		totem->controls_visibility = TOTEM_CONTROLS_VISIBLE;
1639 		show_controls (totem, TRUE);
1640 	}
1641 
1642 	bacon_video_widget_set_fullscreen (totem->bvw,
1643 					   totem->controls_visibility == TOTEM_CONTROLS_FULLSCREEN);
1644 
1645 	action = g_action_map_lookup_action (G_ACTION_MAP (totem), "fullscreen");
1646 	g_simple_action_set_state (G_SIMPLE_ACTION (action),
1647 				   g_variant_new_boolean (totem->controls_visibility == TOTEM_CONTROLS_FULLSCREEN));
1648 
1649 	g_object_notify (G_OBJECT (totem), "fullscreen");
1650 
1651 	return FALSE;
1652 }
1653 
1654 static void
totem_object_action_fullscreen_toggle(TotemObject * totem)1655 totem_object_action_fullscreen_toggle (TotemObject *totem)
1656 {
1657 	if (totem_object_is_fullscreen (totem) != FALSE)
1658 		gtk_window_unfullscreen (GTK_WINDOW (totem->win));
1659 	else
1660 		gtk_window_fullscreen (GTK_WINDOW (totem->win));
1661 }
1662 
1663 /**
1664  * totem_object_set_fullscreen:
1665  * @totem: a #TotemObject
1666  * @state: %TRUE if Totem should be fullscreened
1667  *
1668  * Sets Totem's fullscreen state according to @state.
1669  **/
1670 void
totem_object_set_fullscreen(TotemObject * totem,gboolean state)1671 totem_object_set_fullscreen (TotemObject *totem, gboolean state)
1672 {
1673 	if (totem_object_is_fullscreen (totem) == state)
1674 		return;
1675 
1676 	if (state)
1677 		gtk_window_fullscreen (GTK_WINDOW (totem->win));
1678 	else
1679 		gtk_window_unfullscreen (GTK_WINDOW (totem->win));
1680 }
1681 
1682 void
totem_object_open(TotemObject * totem)1683 totem_object_open (TotemObject *totem)
1684 {
1685 	totem_object_open_dialog (totem, NULL);
1686 }
1687 
1688 static void
totem_open_location_response_cb(GtkDialog * dialog,gint response,TotemObject * totem)1689 totem_open_location_response_cb (GtkDialog *dialog, gint response, TotemObject *totem)
1690 {
1691 	char *uri;
1692 
1693 	if (response != GTK_RESPONSE_OK) {
1694 		gtk_widget_destroy (GTK_WIDGET (totem->open_location));
1695 		return;
1696 	}
1697 
1698 	gtk_widget_hide (GTK_WIDGET (dialog));
1699 
1700 	/* Open the specified URI */
1701 	uri = totem_open_location_get_uri (totem->open_location);
1702 
1703 	if (uri != NULL) {
1704 		totem_grilo_add_item_to_recent (TOTEM_GRILO (totem->grilo), uri, NULL, TRUE);
1705 		g_free (uri);
1706 	}
1707 
1708 	gtk_widget_destroy (GTK_WIDGET (totem->open_location));
1709 }
1710 
1711 void
totem_object_open_location(TotemObject * totem)1712 totem_object_open_location (TotemObject *totem)
1713 {
1714 	if (totem->open_location != NULL) {
1715 		gtk_window_present (GTK_WINDOW (totem->open_location));
1716 		return;
1717 	}
1718 
1719 	totem->open_location = TOTEM_OPEN_LOCATION (totem_open_location_new ());
1720 
1721 	g_signal_connect (G_OBJECT (totem->open_location), "delete-event",
1722 			G_CALLBACK (gtk_widget_destroy), NULL);
1723 	g_signal_connect (G_OBJECT (totem->open_location), "response",
1724 			G_CALLBACK (totem_open_location_response_cb), totem);
1725 	g_object_add_weak_pointer (G_OBJECT (totem->open_location), (gpointer *)&(totem->open_location));
1726 
1727 	gtk_window_set_transient_for (GTK_WINDOW (totem->open_location),
1728 			GTK_WINDOW (totem->win));
1729 	gtk_widget_show (GTK_WIDGET (totem->open_location));
1730 }
1731 
1732 static char *
totem_get_nice_name_for_stream(TotemObject * totem)1733 totem_get_nice_name_for_stream (TotemObject *totem)
1734 {
1735 	GValue title_value = { 0, };
1736 	GValue album_value = { 0, };
1737 	GValue artist_value = { 0, };
1738 	GValue value = { 0, };
1739 	char *retval;
1740 	int tracknum;
1741 
1742 	bacon_video_widget_get_metadata (totem->bvw, BVW_INFO_TITLE, &title_value);
1743 	bacon_video_widget_get_metadata (totem->bvw, BVW_INFO_ARTIST, &artist_value);
1744 	bacon_video_widget_get_metadata (totem->bvw, BVW_INFO_ALBUM, &album_value);
1745 	bacon_video_widget_get_metadata (totem->bvw,
1746 					 BVW_INFO_TRACK_NUMBER,
1747 					 &value);
1748 
1749 	tracknum = g_value_get_int (&value);
1750 	g_value_unset (&value);
1751 
1752 	emit_metadata_updated (totem,
1753 	                       g_value_get_string (&artist_value),
1754 	                       g_value_get_string (&title_value),
1755 	                       g_value_get_string (&album_value),
1756 	                       tracknum);
1757 
1758 	if (g_value_get_string (&title_value) == NULL) {
1759 		retval = NULL;
1760 		goto bail;
1761 	}
1762 	if (g_value_get_string (&artist_value) == NULL) {
1763 		retval = g_value_dup_string (&title_value);
1764 		goto bail;
1765 	}
1766 
1767 	if (tracknum != 0) {
1768 		retval = g_strdup_printf ("%02d. %s - %s",
1769 					  tracknum,
1770 					  g_value_get_string (&artist_value),
1771 					  g_value_get_string (&title_value));
1772 	} else {
1773 		retval = g_strdup_printf ("%s - %s",
1774 					  g_value_get_string (&artist_value),
1775 					  g_value_get_string (&title_value));
1776 	}
1777 
1778 bail:
1779 	g_value_unset (&album_value);
1780 	g_value_unset (&artist_value);
1781 	g_value_unset (&title_value);
1782 
1783 	return retval;
1784 }
1785 
1786 static void
update_mrl_label(TotemObject * totem,const char * name)1787 update_mrl_label (TotemObject *totem, const char *name)
1788 {
1789 	if (name != NULL) {
1790 		/* Update the mrl label */
1791 		g_clear_pointer (&totem->player_title, g_free);
1792 		totem->player_title = g_strdup (name);
1793 	} else {
1794 		bacon_time_label_set_time (totem->time_label,
1795 					   0, 0);
1796 		bacon_time_label_set_time (totem->time_rem_label,
1797 					   0, 0);
1798 
1799 		g_object_notify (G_OBJECT (totem), "stream-length");
1800 
1801 		/* Update the mrl label */
1802 		g_clear_pointer (&totem->player_title, g_free);
1803 	}
1804 
1805 	if (g_strcmp0 (totem_object_get_main_page (totem), "player") == 0)
1806 		g_object_set (totem->header, "title", totem->player_title, NULL);
1807 }
1808 
1809 static void
totem_object_set_next_subtitle(TotemObject * totem,const char * subtitle)1810 totem_object_set_next_subtitle (TotemObject *totem,
1811 				const char  *subtitle)
1812 {
1813 	g_clear_pointer (&totem->next_subtitle, g_free);
1814 	totem->next_subtitle = g_strdup (subtitle);
1815 }
1816 
1817 /**
1818  * totem_object_set_mrl:
1819  * @totem: a #TotemObject
1820  * @mrl: the MRL to play
1821  * @subtitle: a subtitle file to load, or %NULL
1822  *
1823  * Loads the specified @mrl and optionally the specified subtitle
1824  * file. If @subtitle is %NULL Totem will attempt to auto-locate
1825  * any subtitle files for @mrl.
1826  *
1827  * If a stream is already playing, it will be stopped and closed.
1828  *
1829  * Errors will be reported asynchronously.
1830  **/
1831 void
totem_object_set_mrl(TotemObject * totem,const char * mrl,const char * subtitle)1832 totem_object_set_mrl (TotemObject *totem,
1833 		      const char *mrl,
1834 		      const char *subtitle)
1835 {
1836 	if (totem->mrl != NULL) {
1837 		totem->pause_start = FALSE;
1838 
1839 		g_clear_pointer (&totem->mrl, g_free);
1840 		bacon_video_widget_close (totem->bvw);
1841 		emit_file_closed (totem);
1842 		totem->has_played_emitted = FALSE;
1843 		play_pause_set_label (totem, STATE_STOPPED);
1844 		update_fill (totem, -1.0);
1845 	}
1846 
1847 	if (mrl == NULL) {
1848 		play_pause_set_label (totem, STATE_STOPPED);
1849 
1850 		/* Play/Pause */
1851 		totem_object_set_sensitivity2 ("play", FALSE);
1852 
1853 		/* Volume */
1854 		totem_controls_set_sensitivity ("volume_button", FALSE);
1855 		totem->volume_sensitive = FALSE;
1856 
1857 		/* Control popup */
1858 		totem_object_set_sensitivity2 ("next-chapter", FALSE);
1859 		totem_object_set_sensitivity2 ("previous-chapter", FALSE);
1860 
1861 		/* Subtitle selection */
1862 		totem_object_set_sensitivity2 ("select-subtitle", FALSE);
1863 
1864 		/* Set the logo */
1865 		bacon_video_widget_set_logo_mode (totem->bvw, TRUE);
1866 		update_mrl_label (totem, NULL);
1867 
1868 		g_object_notify (G_OBJECT (totem), "playing");
1869 	} else {
1870 		gboolean caps;
1871 		char *user_agent;
1872 		char *autoload_sub;
1873 
1874 		bacon_video_widget_set_logo_mode (totem->bvw, FALSE);
1875 
1876 		autoload_sub = NULL;
1877 		if (subtitle == NULL)
1878 			g_signal_emit (G_OBJECT (totem), totem_table_signals[GET_TEXT_SUBTITLE], 0, mrl, &autoload_sub);
1879 
1880 		user_agent = NULL;
1881 		g_signal_emit (G_OBJECT (totem), totem_table_signals[GET_USER_AGENT], 0, mrl, &user_agent);
1882 		bacon_video_widget_set_user_agent (totem->bvw, user_agent);
1883 		g_free (user_agent);
1884 
1885 		g_application_mark_busy (G_APPLICATION (totem));
1886 		bacon_video_widget_open (totem->bvw, mrl);
1887 		if (subtitle) {
1888 			bacon_video_widget_set_text_subtitle (totem->bvw, subtitle);
1889 		} else if (autoload_sub) {
1890 			bacon_video_widget_set_text_subtitle (totem->bvw, autoload_sub);
1891 			g_free (autoload_sub);
1892 		} else {
1893 			totem_playlist_set_current_subtitle (totem->playlist, totem->next_subtitle);
1894 			totem_object_set_next_subtitle (totem, NULL);
1895 		}
1896 		g_application_unmark_busy (G_APPLICATION (totem));
1897 		totem->mrl = g_strdup (mrl);
1898 
1899 		/* Play/Pause */
1900 		totem_object_set_sensitivity2 ("play", TRUE);
1901 
1902 		/* Volume */
1903 		caps = bacon_video_widget_can_set_volume (totem->bvw);
1904 		totem_controls_set_sensitivity ("volume_button", caps);
1905 		totem->volume_sensitive = caps;
1906 
1907 		/* Subtitle selection */
1908 		totem_object_set_sensitivity2 ("select-subtitle", !totem_is_special_mrl (mrl));
1909 
1910 		/* Set the playlist */
1911 		play_pause_set_label (totem, STATE_PAUSED);
1912 
1913 		emit_file_opened (totem, totem->mrl);
1914 
1915 		totem_object_set_main_page (totem, "player");
1916 	}
1917 
1918 	g_object_notify (G_OBJECT (totem), "current-mrl");
1919 
1920 	update_buttons (totem);
1921 	update_media_menu_items (totem);
1922 }
1923 
1924 static gboolean
totem_time_within_seconds(TotemObject * totem)1925 totem_time_within_seconds (TotemObject *totem)
1926 {
1927 	gint64 _time;
1928 
1929 	_time = bacon_video_widget_get_current_time (totem->bvw);
1930 
1931 	return (_time < REWIND_OR_PREVIOUS);
1932 }
1933 
1934 #define totem_has_direction_track(totem, dir) (dir == TOTEM_PLAYLIST_DIRECTION_NEXT ? bacon_video_widget_has_next_track (totem->bvw) : bacon_video_widget_has_previous_track (totem->bvw))
1935 
1936 static void
totem_object_direction(TotemObject * totem,TotemPlaylistDirection dir)1937 totem_object_direction (TotemObject *totem, TotemPlaylistDirection dir)
1938 {
1939 	if (totem_has_direction_track (totem, dir) == FALSE &&
1940 	    totem_playlist_has_direction (totem->playlist, dir) == FALSE &&
1941 	    totem_playlist_get_repeat (totem->playlist) == FALSE)
1942 		return;
1943 
1944 	if (totem_has_direction_track (totem, dir) != FALSE) {
1945 		BvwDVDEvent event;
1946 		event = (dir == TOTEM_PLAYLIST_DIRECTION_NEXT ? BVW_DVD_NEXT_CHAPTER : BVW_DVD_PREV_CHAPTER);
1947 		bacon_video_widget_dvd_event (totem->bvw, event);
1948 		return;
1949 	}
1950 
1951 	if (dir == TOTEM_PLAYLIST_DIRECTION_NEXT ||
1952 	    bacon_video_widget_is_seekable (totem->bvw) == FALSE ||
1953 	    totem_time_within_seconds (totem) != FALSE) {
1954 		char *mrl, *subtitle;
1955 
1956 		totem_playlist_set_direction (totem->playlist, dir);
1957 		mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
1958 		totem_object_set_mrl_and_play (totem, mrl, subtitle);
1959 
1960 		g_free (subtitle);
1961 		g_free (mrl);
1962 	} else {
1963 		totem_object_seek (totem, 0);
1964 	}
1965 }
1966 
1967 /**
1968  * totem_object_can_seek_previous:
1969  * @totem: a #TotemObject
1970  *
1971  * Returns true if totem_object_seek_previous() would have an effect.
1972  */
1973 gboolean
totem_object_can_seek_previous(TotemObject * totem)1974 totem_object_can_seek_previous (TotemObject *totem)
1975 {
1976 	return bacon_video_widget_has_previous_track (totem->bvw) ||
1977 		totem_playlist_has_previous_mrl (totem->playlist) ||
1978 		totem_playlist_get_repeat (totem->playlist);
1979 }
1980 
1981 /**
1982  * totem_object_seek_previous:
1983  * @totem: a #TotemObject
1984  *
1985  * If a DVD is being played, goes to the previous chapter. If a normal stream
1986  * is being played, goes to the start of the stream if possible. If seeking is
1987  * not possible, plays the previous entry in the playlist.
1988  **/
1989 void
totem_object_seek_previous(TotemObject * totem)1990 totem_object_seek_previous (TotemObject *totem)
1991 {
1992 	totem_object_direction (totem, TOTEM_PLAYLIST_DIRECTION_PREVIOUS);
1993 }
1994 
1995 /**
1996  * totem_object_can_seek_next:
1997  * @totem: a #TotemObject
1998  *
1999  * Returns true if totem_object_seek_next() would have an effect.
2000  */
2001 gboolean
totem_object_can_seek_next(TotemObject * totem)2002 totem_object_can_seek_next (TotemObject *totem)
2003 {
2004 	return bacon_video_widget_has_next_track (totem->bvw) ||
2005 		totem_playlist_has_next_mrl (totem->playlist) ||
2006 		totem_playlist_get_repeat (totem->playlist);
2007 }
2008 
2009 /**
2010  * totem_object_seek_next:
2011  * @totem: a #TotemObject
2012  *
2013  * If a DVD is being played, goes to the next chapter. If a normal stream
2014  * is being played, plays the next entry in the playlist.
2015  **/
2016 void
totem_object_seek_next(TotemObject * totem)2017 totem_object_seek_next (TotemObject *totem)
2018 {
2019 	totem_object_direction (totem, TOTEM_PLAYLIST_DIRECTION_NEXT);
2020 }
2021 
2022 static void
totem_seek_time_rel(TotemObject * totem,gint64 _time,gboolean relative,gboolean accurate)2023 totem_seek_time_rel (TotemObject *totem, gint64 _time, gboolean relative, gboolean accurate)
2024 {
2025 	GError *err = NULL;
2026 	gint64 sec;
2027 
2028 	if (totem->mrl == NULL)
2029 		return;
2030 	if (bacon_video_widget_is_seekable (totem->bvw) == FALSE)
2031 		return;
2032 
2033 	if (relative != FALSE) {
2034 		gint64 oldmsec;
2035 		oldmsec = bacon_video_widget_get_current_time (totem->bvw);
2036 		sec = MAX (0, oldmsec + _time);
2037 	} else {
2038 		sec = _time;
2039 	}
2040 
2041 	bacon_video_widget_seek_time (totem->bvw, sec, accurate, &err);
2042 
2043 	if (err != NULL)
2044 	{
2045 		char *msg, *disp;
2046 
2047 		disp = totem_uri_escape_for_display (totem->mrl);
2048 		msg = g_strdup_printf(_("Totem could not play “%s”."), disp);
2049 		g_free (disp);
2050 
2051 		bacon_video_widget_stop (totem->bvw);
2052 		play_pause_set_label (totem, STATE_STOPPED);
2053 		totem_object_show_error (totem, msg, err->message);
2054 		g_free (msg);
2055 		g_error_free (err);
2056 	}
2057 }
2058 
2059 /**
2060  * totem_object_seek_relative:
2061  * @totem: a #TotemObject
2062  * @offset: the time offset to seek to
2063  * @accurate: whether to use accurate seek, an accurate seek might be slower for some formats (see GStreamer docs)
2064  *
2065  * Seeks to an @offset from the current position in the stream,
2066  * or displays an error dialog if that's not possible.
2067  **/
2068 void
totem_object_seek_relative(TotemObject * totem,gint64 offset,gboolean accurate)2069 totem_object_seek_relative (TotemObject *totem, gint64 offset, gboolean accurate)
2070 {
2071 	totem_seek_time_rel (totem, offset, TRUE, accurate);
2072 }
2073 
2074 /**
2075  * totem_object_seek_time:
2076  * @totem: a #TotemObject
2077  * @msec: the time to seek to
2078  * @accurate: whether to use accurate seek, an accurate seek might be slower for some formats (see GStreamer docs)
2079  *
2080  * Seeks to an absolute time in the stream, or displays an
2081  * error dialog if that's not possible.
2082  **/
2083 void
totem_object_seek_time(TotemObject * totem,gint64 msec,gboolean accurate)2084 totem_object_seek_time (TotemObject *totem, gint64 msec, gboolean accurate)
2085 {
2086 	totem_seek_time_rel (totem, msec, FALSE, accurate);
2087 }
2088 
2089 void
totem_object_set_zoom(TotemObject * totem,gboolean zoom)2090 totem_object_set_zoom (TotemObject *totem,
2091 		       gboolean     zoom)
2092 {
2093 	GAction *action;
2094 
2095 	action = g_action_map_lookup_action (G_ACTION_MAP (totem), "zoom");
2096 	g_action_change_state (action, g_variant_new_boolean (zoom));
2097 }
2098 
2099 /**
2100  * totem_object_get_volume:
2101  * @totem: a #TotemObject
2102  *
2103  * Gets the current volume level, as a value between <code class="literal">0.0</code> and <code class="literal">1.0</code>.
2104  *
2105  * Return value: the volume level
2106  **/
2107 double
totem_object_get_volume(TotemObject * totem)2108 totem_object_get_volume (TotemObject *totem)
2109 {
2110 	return bacon_video_widget_get_volume (totem->bvw);
2111 }
2112 
2113 /**
2114  * totem_object_set_volume:
2115  * @totem: a #TotemObject
2116  * @volume: the new absolute volume value
2117  *
2118  * Sets the volume, with <code class="literal">1.0</code> being the maximum, and <code class="literal">0.0</code> being the minimum level.
2119  **/
2120 void
totem_object_set_volume(TotemObject * totem,double volume)2121 totem_object_set_volume (TotemObject *totem, double volume)
2122 {
2123 	if (bacon_video_widget_can_set_volume (totem->bvw) == FALSE)
2124 		return;
2125 
2126 	bacon_video_widget_set_volume (totem->bvw, volume);
2127 }
2128 
2129 /**
2130  * totem_object_get_rate:
2131  * @totem: a #TotemObject
2132  *
2133  * Gets the current playback rate, with `1.0` being the normal playback rate.
2134  *
2135  * Return value: the volume level
2136  **/
2137 float
totem_object_get_rate(TotemObject * totem)2138 totem_object_get_rate (TotemObject *totem)
2139 {
2140 	return bacon_video_widget_get_rate (totem->bvw);
2141 }
2142 
2143 /**
2144  * totem_object_set_rate:
2145  * @totem: a #TotemObject
2146  * @rate: the new absolute playback rate
2147  *
2148  * Sets the playback rate, with `1.0` being the normal playback rate.
2149  *
2150  * Return value: %TRUE on success, %FALSE on failure.
2151  **/
2152 gboolean
totem_object_set_rate(TotemObject * totem,float rate)2153 totem_object_set_rate (TotemObject *totem, float rate)
2154 {
2155 	return bacon_video_widget_set_rate (totem->bvw, rate);
2156 }
2157 
2158 /**
2159  * totem_object_set_volume_relative:
2160  * @totem: a #TotemObject
2161  * @off_pct: the value by which to increase or decrease the volume
2162  *
2163  * Sets the volume relative to its current level, with <code class="literal">1.0</code> being the
2164  * maximum, and <code class="literal">0.0</code> being the minimum level.
2165  **/
2166 void
totem_object_set_volume_relative(TotemObject * totem,double off_pct)2167 totem_object_set_volume_relative (TotemObject *totem, double off_pct)
2168 {
2169 	double vol;
2170 
2171 	if (bacon_video_widget_can_set_volume (totem->bvw) == FALSE)
2172 		return;
2173 	if (totem->muted != FALSE)
2174 		totem_object_volume_toggle_mute (totem);
2175 
2176 	vol = bacon_video_widget_get_volume (totem->bvw);
2177 	bacon_video_widget_set_volume (totem->bvw, vol + off_pct);
2178 }
2179 
2180 /**
2181  * totem_object_volume_toggle_mute:
2182  * @totem: a #TotemObject
2183  *
2184  * Toggles the mute status.
2185  **/
2186 void
totem_object_volume_toggle_mute(TotemObject * totem)2187 totem_object_volume_toggle_mute (TotemObject *totem)
2188 {
2189 	if (totem->muted == FALSE) {
2190 		totem->muted = TRUE;
2191 		totem->prev_volume = bacon_video_widget_get_volume (totem->bvw);
2192 		bacon_video_widget_set_volume (totem->bvw, 0.0);
2193 	} else {
2194 		totem->muted = FALSE;
2195 		bacon_video_widget_set_volume (totem->bvw, totem->prev_volume);
2196 	}
2197 }
2198 
2199 static void
totem_object_toggle_aspect_ratio(TotemObject * totem)2200 totem_object_toggle_aspect_ratio (TotemObject *totem)
2201 {
2202 	GAction *action;
2203 	int tmp;
2204 
2205 	tmp = bacon_video_widget_get_aspect_ratio (totem->bvw);
2206 	tmp++;
2207 	if (tmp > BVW_RATIO_DVB)
2208 		tmp = BVW_RATIO_AUTO;
2209 
2210 	action = g_action_map_lookup_action (G_ACTION_MAP (totem), "aspect-ratio");
2211 	g_action_change_state (action, g_variant_new ("i", tmp));
2212 }
2213 
2214 void
totem_object_show_help(TotemObject * totem)2215 totem_object_show_help (TotemObject *totem)
2216 {
2217 	GError *error = NULL;
2218 
2219 	if (gtk_show_uri_on_window (GTK_WINDOW (totem->win), "help:totem", gtk_get_current_event_time (), &error) == FALSE) {
2220 		totem_object_show_error (totem, _("Totem could not display the help contents."), error->message);
2221 		g_error_free (error);
2222 	}
2223 }
2224 
2225 void
totem_object_show_keyboard_shortcuts(TotemObject * totem)2226 totem_object_show_keyboard_shortcuts (TotemObject *totem)
2227 {
2228 	GtkBuilder *builder;
2229 
2230 	if (totem->shortcuts_win) {
2231 		gtk_window_present (totem->shortcuts_win);
2232 		return;
2233 	}
2234 
2235 	builder = totem_interface_load ("shortcuts.ui", FALSE, NULL, NULL);
2236 	totem->shortcuts_win = GTK_WINDOW (gtk_builder_get_object (builder, "shortcuts-totem"));
2237 	gtk_window_set_transient_for (totem->shortcuts_win, GTK_WINDOW (totem->win));
2238 
2239 	g_signal_connect (totem->shortcuts_win, "destroy",
2240 			  G_CALLBACK (gtk_widget_destroyed), &totem->shortcuts_win);
2241 
2242 	gtk_widget_show (GTK_WIDGET (totem->shortcuts_win));
2243 	g_object_unref (builder);
2244 }
2245 
2246 /* This is called in the main thread */
2247 static void
totem_object_drop_files_finished(TotemPlaylist * playlist,GAsyncResult * result,TotemObject * totem)2248 totem_object_drop_files_finished (TotemPlaylist *playlist, GAsyncResult *result, TotemObject *totem)
2249 {
2250 	char *mrl, *subtitle;
2251 
2252 	/* Reconnect the playlist's changed signal (which was disconnected below in totem_object_drop_files(). */
2253 	g_signal_connect (G_OBJECT (playlist), "changed", G_CALLBACK (playlist_changed_cb), totem);
2254 	mrl = totem_playlist_get_current_mrl (playlist, &subtitle);
2255 	totem_object_set_mrl_and_play (totem, mrl, subtitle);
2256 	g_free (mrl);
2257 	g_free (subtitle);
2258 
2259 	g_object_unref (totem);
2260 }
2261 
2262 static gboolean
totem_object_drop_files(TotemObject * totem,GtkSelectionData * data,int drop_type)2263 totem_object_drop_files (TotemObject      *totem,
2264 			 GtkSelectionData *data,
2265 			 int               drop_type)
2266 {
2267 	char **list;
2268 	guint i, len;
2269 	GList *p, *file_list, *mrl_list = NULL;
2270 
2271 	list = g_uri_list_extract_uris ((const char *) gtk_selection_data_get_data (data));
2272 	file_list = NULL;
2273 
2274 	for (i = 0; list[i] != NULL; i++) {
2275 		char *filename;
2276 
2277 		if (list[i] == NULL)
2278 			continue;
2279 
2280 		filename = totem_create_full_path (list[i]);
2281 		file_list = g_list_prepend (file_list,
2282 					    filename ? filename : g_strdup (list[i]));
2283 	}
2284 	g_strfreev (list);
2285 
2286 	if (file_list == NULL)
2287 		return FALSE;
2288 
2289 	if (drop_type != 1)
2290 		file_list = g_list_sort (file_list, (GCompareFunc) strcmp);
2291 	else
2292 		file_list = g_list_reverse (file_list);
2293 
2294 	/* How many files? Check whether those could be subtitles */
2295 	len = g_list_length (file_list);
2296 	if (len == 1 || (len == 2 && drop_type == 1)) {
2297 		if (totem_uri_is_subtitle (file_list->data) != FALSE) {
2298 			totem_playlist_set_current_subtitle (totem->playlist, file_list->data);
2299 			goto bail;
2300 		}
2301 	}
2302 
2303 	/* The function that calls us knows better if we should be doing something with the changed playlist... */
2304 	g_signal_handlers_disconnect_by_func (G_OBJECT (totem->playlist), playlist_changed_cb, totem);
2305 	totem_playlist_clear (totem->playlist);
2306 
2307 	/* Add each MRL to the playlist asynchronously */
2308 	for (p = file_list; p != NULL; p = p->next) {
2309 		const char *filename, *title;
2310 
2311 		filename = p->data;
2312 		title = NULL;
2313 
2314 		/* Super _NETSCAPE_URL trick */
2315 		if (drop_type == 1) {
2316 			p = p->next;
2317 			if (p != NULL) {
2318 				if (g_str_has_prefix (p->data, "File:") != FALSE)
2319 					title = (char *)p->data + 5;
2320 				else
2321 					title = p->data;
2322 			}
2323 		}
2324 
2325 		/* Add the MRL data to the list of MRLs to add to the playlist */
2326 		mrl_list = g_list_prepend (mrl_list, totem_playlist_mrl_data_new (filename, title));
2327 	}
2328 
2329 	/* Add the MRLs to the playlist asynchronously and in order. We need to reconnect playlist's "changed" signal once all of the add-MRL
2330 	 * operations have completed. */
2331 	if (mrl_list != NULL) {
2332 		totem_playlist_add_mrls (totem->playlist, g_list_reverse (mrl_list), TRUE, NULL,
2333 		                         (GAsyncReadyCallback) totem_object_drop_files_finished, g_object_ref (totem));
2334 	}
2335 
2336 bail:
2337 	g_list_free_full (file_list, g_free);
2338 
2339 	return TRUE;
2340 }
2341 
2342 static void
drop_video_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint _time,Totem * totem)2343 drop_video_cb (GtkWidget          *widget,
2344 	       GdkDragContext     *context,
2345 	       gint                x,
2346 	       gint                y,
2347 	       GtkSelectionData   *data,
2348 	       guint               info,
2349 	       guint               _time,
2350 	       Totem              *totem)
2351 {
2352 	GtkWidget *source_widget;
2353 	GdkDragAction action = gdk_drag_context_get_selected_action (context);
2354 
2355 	source_widget = gtk_drag_get_source_widget (context);
2356 
2357 	/* Drop of video on itself */
2358 	if (source_widget && widget == source_widget && action == GDK_ACTION_MOVE) {
2359 		gtk_drag_finish (context, FALSE, FALSE, _time);
2360 		return;
2361 	}
2362 
2363 	totem_object_drop_files (totem, data, info);
2364 	gtk_drag_finish (context, TRUE, FALSE, _time);
2365 	return;
2366 }
2367 
2368 static void
back_button_clicked_cb(GtkButton * button,TotemObject * totem)2369 back_button_clicked_cb (GtkButton   *button,
2370 			TotemObject *totem)
2371 {
2372 	if (g_strcmp0 (totem_object_get_main_page (totem), "player") == 0) {
2373 		totem_playlist_clear (totem->playlist);
2374 		gtk_window_unfullscreen (GTK_WINDOW (totem->win));
2375 		totem_object_set_main_page (totem, "grilo");
2376 	} else {
2377 		totem_grilo_back_button_clicked (TOTEM_GRILO (totem->grilo));
2378 	}
2379 }
2380 
2381 static void
on_got_redirect(BaconVideoWidget * bvw,const char * mrl,TotemObject * totem)2382 on_got_redirect (BaconVideoWidget *bvw, const char *mrl, TotemObject *totem)
2383 {
2384 	char *new_mrl;
2385 
2386 	if (strstr (mrl, "://") != NULL) {
2387 		new_mrl = NULL;
2388 	} else {
2389 		GFile *old_file, *parent, *new_file;
2390 		char *old_mrl;
2391 
2392 		/* Get the parent for the current MRL, that's our base */
2393 		old_mrl = totem_playlist_get_current_mrl (TOTEM_PLAYLIST (totem->playlist), NULL);
2394 		old_file = g_file_new_for_uri (old_mrl);
2395 		g_free (old_mrl);
2396 		parent = g_file_get_parent (old_file);
2397 		g_object_unref (old_file);
2398 
2399 		/* Resolve the URL */
2400 		new_file = g_file_get_child (parent, mrl);
2401 		g_object_unref (parent);
2402 
2403 		new_mrl = g_file_get_uri (new_file);
2404 		g_object_unref (new_file);
2405 	}
2406 
2407 	bacon_video_widget_close (totem->bvw);
2408 	emit_file_closed (totem);
2409 	totem->has_played_emitted = FALSE;
2410 	g_application_mark_busy (G_APPLICATION (totem));
2411 	bacon_video_widget_open (totem->bvw, new_mrl ? new_mrl : mrl);
2412 	emit_file_opened (totem, new_mrl ? new_mrl : mrl);
2413 	g_application_unmark_busy (G_APPLICATION (totem));
2414 	if (bacon_video_widget_play (bvw, NULL) != FALSE) {
2415 		totem_file_has_played (totem, totem->mrl);
2416 		totem->has_played_emitted = TRUE;
2417 	}
2418 	g_free (new_mrl);
2419 }
2420 
2421 static void
on_channels_change_event(BaconVideoWidget * bvw,TotemObject * totem)2422 on_channels_change_event (BaconVideoWidget *bvw, TotemObject *totem)
2423 {
2424 	gchar *name;
2425 
2426 	totem_sublang_update (totem);
2427 	update_media_menu_items (totem);
2428 
2429 	/* updated stream info (new song) */
2430 	name = totem_get_nice_name_for_stream (totem);
2431 
2432 	if (name != NULL) {
2433 		update_mrl_label (totem, name);
2434 		totem_playlist_set_title
2435 			(TOTEM_PLAYLIST (totem->playlist), name);
2436 		g_free (name);
2437 	}
2438 }
2439 
2440 static void
on_playlist_change_name(TotemPlaylist * playlist,TotemObject * totem)2441 on_playlist_change_name (TotemPlaylist *playlist, TotemObject *totem)
2442 {
2443 	char *name;
2444 
2445 	name = totem_playlist_get_current_title (playlist);
2446 	if (name != NULL) {
2447 		update_mrl_label (totem, name);
2448 		g_free (name);
2449 	}
2450 }
2451 
2452 static void
on_got_metadata_event(BaconVideoWidget * bvw,TotemObject * totem)2453 on_got_metadata_event (BaconVideoWidget *bvw, TotemObject *totem)
2454 {
2455         char *name;
2456 
2457 	name = totem_get_nice_name_for_stream (totem);
2458 
2459 	if (name != NULL) {
2460 		totem_playlist_set_title
2461 			(TOTEM_PLAYLIST (totem->playlist), name);
2462 		g_free (name);
2463 	}
2464 
2465 	totem_sublang_update (totem);
2466 	update_buttons (totem);
2467 	on_playlist_change_name (TOTEM_PLAYLIST (totem->playlist), totem);
2468 }
2469 
2470 static void
on_error_event(BaconVideoWidget * bvw,char * message,gboolean playback_stopped,TotemObject * totem)2471 on_error_event (BaconVideoWidget *bvw, char *message,
2472                 gboolean playback_stopped, TotemObject *totem)
2473 {
2474 	/* Clear the seek if it's there, we only want to try and seek
2475 	 * the first file, even if it's not there */
2476 	totem_playlist_steal_current_starttime (totem->playlist);
2477 	totem->pause_start = FALSE;
2478 
2479 	if (playback_stopped)
2480 		play_pause_set_label (totem, STATE_STOPPED);
2481 
2482 	totem_object_show_error (totem, _("An error occurred"), message);
2483 }
2484 
2485 static void
on_buffering_event(BaconVideoWidget * bvw,gdouble percentage,TotemObject * totem)2486 on_buffering_event (BaconVideoWidget *bvw, gdouble percentage, TotemObject *totem)
2487 {
2488 	//FIXME show that somehow
2489 }
2490 
2491 static void
on_download_buffering_event(BaconVideoWidget * bvw,gdouble level,TotemObject * totem)2492 on_download_buffering_event (BaconVideoWidget *bvw, gdouble level, TotemObject *totem)
2493 {
2494 	update_fill (totem, level);
2495 }
2496 
2497 static void
update_fill(TotemObject * totem,gdouble level)2498 update_fill (TotemObject *totem, gdouble level)
2499 {
2500 	if (level < 0.0) {
2501 		gtk_range_set_show_fill_level (GTK_RANGE (totem->seek), FALSE);
2502 	} else {
2503 		gtk_range_set_fill_level (GTK_RANGE (totem->seek), level * 65535.0f);
2504 		gtk_range_set_show_fill_level (GTK_RANGE (totem->seek), TRUE);
2505 	}
2506 }
2507 
2508 static void
update_seekable(TotemObject * totem)2509 update_seekable (TotemObject *totem)
2510 {
2511 	gboolean seekable;
2512 	gboolean notify;
2513 
2514 	seekable = bacon_video_widget_is_seekable (totem->bvw);
2515 	notify = (totem->seekable == seekable);
2516 	totem->seekable = seekable;
2517 
2518 	/* Check if the stream is seekable */
2519 	gtk_widget_set_sensitive (totem->seek, seekable);
2520 
2521 	if (seekable != FALSE) {
2522 		gint64 starttime;
2523 
2524 		starttime = totem_playlist_steal_current_starttime (totem->playlist);
2525 		if (starttime != 0) {
2526 			bacon_video_widget_seek_time (totem->bvw,
2527 						      starttime * 1000, FALSE, NULL);
2528 			if (totem->pause_start) {
2529 				totem_object_pause (totem);
2530 				totem->pause_start = FALSE;
2531 			}
2532 		}
2533 	}
2534 
2535 	if (notify)
2536 		g_object_notify (G_OBJECT (totem), "seekable");
2537 }
2538 
2539 static void
update_slider_visibility(TotemObject * totem,gint64 stream_length)2540 update_slider_visibility (TotemObject *totem,
2541 			  gint64 stream_length)
2542 {
2543 	if (totem->stream_length == stream_length)
2544 		return;
2545 	if (totem->stream_length > 0 && stream_length > 0)
2546 		return;
2547 	if (stream_length != 0)
2548 		gtk_range_set_range (GTK_RANGE (totem->seek), 0., 65535.);
2549 	else
2550 		gtk_range_set_range (GTK_RANGE (totem->seek), 0., 0.);
2551 }
2552 
2553 static void
update_current_time(BaconVideoWidget * bvw,gint64 current_time,gint64 stream_length,double current_position,gboolean seekable,TotemObject * totem)2554 update_current_time (BaconVideoWidget *bvw,
2555 		     gint64            current_time,
2556 		     gint64            stream_length,
2557 		     double            current_position,
2558 		     gboolean          seekable,
2559 		     TotemObject      *totem)
2560 {
2561 	update_slider_visibility (totem, stream_length);
2562 
2563 	if (totem->seek_lock == FALSE) {
2564 		gtk_adjustment_set_value (totem->seekadj,
2565 					  current_position * 65535);
2566 
2567 		if (stream_length == 0 && totem->mrl != NULL) {
2568 			bacon_time_label_set_time (totem->time_label,
2569 						   current_time, -1);
2570 			bacon_time_label_set_time (totem->time_rem_label,
2571 						   current_time, -1);
2572 		} else {
2573 			bacon_time_label_set_time (totem->time_label,
2574 						   current_time,
2575 						   stream_length);
2576 			bacon_time_label_set_time (totem->time_rem_label,
2577 						   current_time,
2578 						   stream_length);
2579 		}
2580 	}
2581 
2582 	if (totem->stream_length != stream_length) {
2583 		g_object_notify (G_OBJECT (totem), "stream-length");
2584 		totem->stream_length = stream_length;
2585 	}
2586 }
2587 
2588 static void
volume_button_value_changed_cb(GtkScaleButton * button,gdouble value,TotemObject * totem)2589 volume_button_value_changed_cb (GtkScaleButton *button, gdouble value, TotemObject *totem)
2590 {
2591 	totem->muted = FALSE;
2592 	bacon_video_widget_set_volume (totem->bvw, value);
2593 }
2594 
2595 static void
update_volume_sliders(TotemObject * totem)2596 update_volume_sliders (TotemObject *totem)
2597 {
2598 	double volume;
2599 
2600 	volume = bacon_video_widget_get_volume (totem->bvw);
2601 
2602 	g_signal_handlers_block_by_func (totem->volume, volume_button_value_changed_cb, totem);
2603 	gtk_scale_button_set_value (GTK_SCALE_BUTTON (totem->volume), volume);
2604 	g_signal_handlers_unblock_by_func (totem->volume, volume_button_value_changed_cb, totem);
2605 }
2606 
2607 static void
property_notify_cb_volume(BaconVideoWidget * bvw,GParamSpec * spec,TotemObject * totem)2608 property_notify_cb_volume (BaconVideoWidget *bvw, GParamSpec *spec, TotemObject *totem)
2609 {
2610 	update_volume_sliders (totem);
2611 }
2612 
2613 static void
property_notify_cb_seekable(BaconVideoWidget * bvw,GParamSpec * spec,TotemObject * totem)2614 property_notify_cb_seekable (BaconVideoWidget *bvw, GParamSpec *spec, TotemObject *totem)
2615 {
2616 	update_seekable (totem);
2617 }
2618 
2619 gboolean
seek_slider_pressed_cb(GtkWidget * widget,GdkEventButton * event,TotemObject * totem)2620 seek_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, TotemObject *totem)
2621 {
2622 	/* HACK: we want the behaviour you get with the left button, so we
2623 	 * mangle the event.  clicking with other buttons moves the slider in
2624 	 * step increments, clicking with the left button moves the slider to
2625 	 * the location of the click.
2626 	 */
2627 	event->button = GDK_BUTTON_PRIMARY;
2628 
2629 	totem->seek_lock = TRUE;
2630 	bacon_video_widget_mark_popup_busy (totem->bvw, "seek started");
2631 
2632 	return FALSE;
2633 }
2634 
2635 void
seek_slider_changed_cb(GtkAdjustment * adj,TotemObject * totem)2636 seek_slider_changed_cb (GtkAdjustment *adj, TotemObject *totem)
2637 {
2638 	double pos;
2639 	gint _time;
2640 
2641 	if (totem->seek_lock == FALSE)
2642 		return;
2643 
2644 	pos = gtk_adjustment_get_value (adj) / 65535;
2645 	_time = bacon_video_widget_get_stream_length (totem->bvw);
2646 
2647 	bacon_time_label_set_time (totem->time_label,
2648 				   pos * _time, _time);
2649 	bacon_time_label_set_time (totem->time_rem_label,
2650 				   pos * _time, _time);
2651 
2652 	if (bacon_video_widget_can_direct_seek (totem->bvw) != FALSE)
2653 		totem_object_seek (totem, pos);
2654 }
2655 
2656 gboolean
seek_slider_released_cb(GtkWidget * widget,GdkEventButton * event,TotemObject * totem)2657 seek_slider_released_cb (GtkWidget *widget, GdkEventButton *event, TotemObject *totem)
2658 {
2659 	GtkAdjustment *adj;
2660 	gdouble val;
2661 
2662 	/* HACK: see seek_slider_pressed_cb */
2663 	event->button = GDK_BUTTON_PRIMARY;
2664 
2665 	/* set to FALSE here to avoid triggering a final seek when
2666 	 * syncing the adjustments while being in direct seek mode */
2667 	totem->seek_lock = FALSE;
2668 	bacon_video_widget_unmark_popup_busy (totem->bvw, "seek started");
2669 
2670 	/* sync both adjustments */
2671 	adj = gtk_range_get_adjustment (GTK_RANGE (widget));
2672 	val = gtk_adjustment_get_value (adj);
2673 
2674 	if (bacon_video_widget_can_direct_seek (totem->bvw) == FALSE)
2675 		totem_object_seek (totem, val / 65535.0);
2676 
2677 	return FALSE;
2678 }
2679 
2680 gboolean
totem_object_open_files(TotemObject * totem,char ** list)2681 totem_object_open_files (TotemObject *totem, char **list)
2682 {
2683 	GSList *slist = NULL;
2684 	int i, retval;
2685 
2686 	for (i = 0 ; list[i] != NULL; i++)
2687 		slist = g_slist_prepend (slist, list[i]);
2688 
2689 	slist = g_slist_reverse (slist);
2690 	retval = totem_object_open_files_list (totem, slist);
2691 	g_slist_free (slist);
2692 
2693 	return retval;
2694 }
2695 
2696 static gboolean
totem_object_open_files_list(TotemObject * totem,GSList * list)2697 totem_object_open_files_list (TotemObject *totem, GSList *list)
2698 {
2699 	GSList *l;
2700 	GList *mrl_list = NULL;
2701 	gboolean changed;
2702 	gboolean cleared;
2703 
2704 	changed = FALSE;
2705 	cleared = FALSE;
2706 
2707 	if (list == NULL)
2708 		return changed;
2709 
2710 	g_application_mark_busy (G_APPLICATION (totem));
2711 
2712 	for (l = list ; l != NULL; l = l->next)
2713 	{
2714 		char *filename;
2715 		char *data = l->data;
2716 
2717 		if (data == NULL)
2718 			continue;
2719 
2720 		/* Ignore relatives paths that start with "--", tough luck */
2721 		if (data[0] == '-' && data[1] == '-')
2722 			continue;
2723 
2724 		/* Get the subtitle part out for our tests */
2725 		filename = totem_create_full_path (data);
2726 		if (filename == NULL)
2727 			filename = g_strdup (data);
2728 
2729 		if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)
2730 				|| strstr (filename, "#") != NULL
2731 				|| strstr (filename, "://") != NULL
2732 				|| g_str_has_prefix (filename, "dvd:") != FALSE
2733 				|| g_str_has_prefix (filename, "vcd:") != FALSE
2734 				|| g_str_has_prefix (filename, "dvb:") != FALSE)
2735 		{
2736 			if (cleared == FALSE)
2737 			{
2738 				/* The function that calls us knows better
2739 				 * if we should be doing something with the
2740 				 * changed playlist ... */
2741 				g_signal_handlers_disconnect_by_func
2742 					(G_OBJECT (totem->playlist),
2743 					 playlist_changed_cb, totem);
2744 				changed = totem_playlist_clear (totem->playlist);
2745 				bacon_video_widget_close (totem->bvw);
2746 				emit_file_closed (totem);
2747 				totem->has_played_emitted = FALSE;
2748 				cleared = TRUE;
2749 			}
2750 
2751 			if (g_str_has_prefix (filename, "dvb:/") != FALSE) {
2752 				mrl_list = g_list_prepend (mrl_list, totem_playlist_mrl_data_new (data, NULL));
2753 				changed = TRUE;
2754 			} else {
2755 				mrl_list = g_list_prepend (mrl_list, totem_playlist_mrl_data_new (filename, NULL));
2756 				changed = TRUE;
2757 			}
2758 		}
2759 
2760 		g_free (filename);
2761 	}
2762 
2763 	/* Add the MRLs to the playlist asynchronously and in order */
2764 	if (mrl_list != NULL)
2765 		totem_playlist_add_mrls (totem->playlist, g_list_reverse (mrl_list), FALSE, NULL, NULL, NULL);
2766 
2767 	g_application_unmark_busy (G_APPLICATION (totem));
2768 
2769 	/* ... and reconnect because we're nice people */
2770 	if (cleared != FALSE)
2771 	{
2772 		g_signal_connect (G_OBJECT (totem->playlist),
2773 				"changed", G_CALLBACK (playlist_changed_cb),
2774 				totem);
2775 	}
2776 
2777 	return changed;
2778 }
2779 
2780 void
show_controls(TotemObject * totem,gboolean was_fullscreen)2781 show_controls (TotemObject *totem, gboolean was_fullscreen)
2782 {
2783 	GtkWidget *bvw_box;
2784 
2785 	if (totem->bvw == NULL)
2786 		return;
2787 
2788 	bvw_box = GTK_WIDGET (gtk_builder_get_object (totem->xml, "tmw_bvw_box"));
2789 
2790 	if (totem->controls_visibility == TOTEM_CONTROLS_VISIBLE) {
2791 		totem_object_save_size (totem);
2792 	} else {
2793 		 /* We won't show controls in fullscreen */
2794 		gtk_container_set_border_width (GTK_CONTAINER (bvw_box), 0);
2795 	}
2796 }
2797 
2798 /**
2799  * totem_object_next_angle:
2800  * @totem: a #TotemObject
2801  *
2802  * Switches to the next angle, if watching a DVD. If not watching a DVD, this is a
2803  * no-op.
2804  **/
2805 void
totem_object_next_angle(TotemObject * totem)2806 totem_object_next_angle (TotemObject *totem)
2807 {
2808 	bacon_video_widget_set_next_angle (totem->bvw);
2809 }
2810 
2811 /**
2812  * totem_object_remote_command:
2813  * @totem: a #TotemObject
2814  * @cmd: a #TotemRemoteCommand
2815  * @url: an MRL to play, or %NULL
2816  *
2817  * Executes the specified @cmd on this instance of Totem. If @cmd
2818  * is an operation requiring an MRL, @url is required; it can be %NULL
2819  * otherwise.
2820  *
2821  * If Totem's fullscreened and the operation is executed correctly,
2822  * the controls will appear as if the user had moved the mouse.
2823  **/
2824 void
totem_object_remote_command(TotemObject * totem,TotemRemoteCommand cmd,const char * url)2825 totem_object_remote_command (TotemObject *totem, TotemRemoteCommand cmd, const char *url)
2826 {
2827 	switch (cmd) {
2828 	case TOTEM_REMOTE_COMMAND_PLAY:
2829 		totem_object_play (totem);
2830 		break;
2831 	case TOTEM_REMOTE_COMMAND_PLAYPAUSE:
2832 		totem_object_play_pause (totem);
2833 		break;
2834 	case TOTEM_REMOTE_COMMAND_PAUSE:
2835 		totem_object_pause (totem);
2836 		break;
2837 	case TOTEM_REMOTE_COMMAND_STOP:
2838 		totem_object_stop (totem);
2839 		break;
2840 	case TOTEM_REMOTE_COMMAND_SEEK_FORWARD: {
2841 		double offset = 0;
2842 
2843 		if (url != NULL)
2844 			offset = g_ascii_strtod (url, NULL);
2845 		if (offset == 0) {
2846 			totem_object_seek_relative (totem, SEEK_FORWARD_OFFSET * 1000, FALSE);
2847 		} else {
2848 			totem_object_seek_relative (totem, offset * 1000, FALSE);
2849 		}
2850 		break;
2851 	}
2852 	case TOTEM_REMOTE_COMMAND_SEEK_BACKWARD: {
2853 		double offset = 0;
2854 
2855 		if (url != NULL)
2856 			offset = g_ascii_strtod (url, NULL);
2857 		if (offset == 0)
2858 			totem_object_seek_relative (totem, SEEK_BACKWARD_OFFSET * 1000, FALSE);
2859 		else
2860 			totem_object_seek_relative (totem,  - (offset * 1000), FALSE);
2861 		break;
2862 	}
2863 	case TOTEM_REMOTE_COMMAND_VOLUME_UP:
2864 		totem_object_set_volume_relative (totem, VOLUME_UP_OFFSET);
2865 		break;
2866 	case TOTEM_REMOTE_COMMAND_VOLUME_DOWN:
2867 		totem_object_set_volume_relative (totem, VOLUME_DOWN_OFFSET);
2868 		break;
2869 	case TOTEM_REMOTE_COMMAND_NEXT:
2870 		totem_object_seek_next (totem);
2871 		break;
2872 	case TOTEM_REMOTE_COMMAND_PREVIOUS:
2873 		totem_object_seek_previous (totem);
2874 		break;
2875 	case TOTEM_REMOTE_COMMAND_FULLSCREEN:
2876 		if (g_strcmp0 (totem_object_get_main_page (totem), "player") == 0)
2877 			totem_object_action_fullscreen_toggle (totem);
2878 		break;
2879 	case TOTEM_REMOTE_COMMAND_QUIT:
2880 		totem_object_exit (totem);
2881 		break;
2882 	case TOTEM_REMOTE_COMMAND_ENQUEUE:
2883 		g_assert (url != NULL);
2884 		if (!totem_uri_is_subtitle (url))
2885 			totem_playlist_add_mrl (totem->playlist, url, NULL, TRUE, NULL, NULL, NULL);
2886 		else
2887 			totem_object_set_next_subtitle (totem, url);
2888 		break;
2889 	case TOTEM_REMOTE_COMMAND_REPLACE:
2890 		if (url == NULL ||
2891 		    !totem_uri_is_subtitle (url)) {
2892 			totem_playlist_clear (totem->playlist);
2893 			if (url == NULL) {
2894 				bacon_video_widget_close (totem->bvw);
2895 				emit_file_closed (totem);
2896 				totem->has_played_emitted = FALSE;
2897 				totem_object_set_mrl (totem, NULL, NULL);
2898 				break;
2899 			}
2900 			totem_playlist_add_mrl (totem->playlist, url, NULL, TRUE, NULL, NULL, NULL);
2901 		} else if (totem->mrl != NULL) {
2902 			totem_playlist_set_current_subtitle (totem->playlist, url);
2903 		} else {
2904 			totem_object_set_next_subtitle (totem, url);
2905 		}
2906 		break;
2907 	case TOTEM_REMOTE_COMMAND_SHOW:
2908 		gtk_window_present_with_time (GTK_WINDOW (totem->win), GDK_CURRENT_TIME);
2909 		break;
2910 	case TOTEM_REMOTE_COMMAND_UP:
2911 		bacon_video_widget_dvd_event (totem->bvw,
2912 				BVW_DVD_ROOT_MENU_UP);
2913 		break;
2914 	case TOTEM_REMOTE_COMMAND_DOWN:
2915 		bacon_video_widget_dvd_event (totem->bvw,
2916 				BVW_DVD_ROOT_MENU_DOWN);
2917 		break;
2918 	case TOTEM_REMOTE_COMMAND_LEFT:
2919 		bacon_video_widget_dvd_event (totem->bvw,
2920 				BVW_DVD_ROOT_MENU_LEFT);
2921 		break;
2922 	case TOTEM_REMOTE_COMMAND_RIGHT:
2923 		bacon_video_widget_dvd_event (totem->bvw,
2924 				BVW_DVD_ROOT_MENU_RIGHT);
2925 		break;
2926 	case TOTEM_REMOTE_COMMAND_SELECT:
2927 		bacon_video_widget_dvd_event (totem->bvw,
2928 				BVW_DVD_ROOT_MENU_SELECT);
2929 		break;
2930 	case TOTEM_REMOTE_COMMAND_DVD_MENU:
2931 		bacon_video_widget_dvd_event (totem->bvw,
2932 				BVW_DVD_ROOT_MENU);
2933 		break;
2934 	case TOTEM_REMOTE_COMMAND_ZOOM_UP:
2935 		totem_object_set_zoom (totem, TRUE);
2936 		break;
2937 	case TOTEM_REMOTE_COMMAND_ZOOM_DOWN:
2938 		totem_object_set_zoom (totem, FALSE);
2939 		break;
2940 	case TOTEM_REMOTE_COMMAND_EJECT:
2941 		totem_object_eject (totem);
2942 		break;
2943 	case TOTEM_REMOTE_COMMAND_PLAY_DVD:
2944 		if (g_strcmp0 (totem_object_get_main_page (totem), "player") == 0)
2945 			back_button_clicked_cb (NULL, totem);
2946 		totem_grilo_set_current_page (TOTEM_GRILO (totem->grilo), TOTEM_GRILO_PAGE_RECENT);
2947 		break;
2948 	case TOTEM_REMOTE_COMMAND_MUTE:
2949 		totem_object_volume_toggle_mute (totem);
2950 		break;
2951 	case TOTEM_REMOTE_COMMAND_TOGGLE_ASPECT:
2952 		totem_object_toggle_aspect_ratio (totem);
2953 		break;
2954 	case TOTEM_REMOTE_COMMAND_UNKNOWN:
2955 	default:
2956 		break;
2957 	}
2958 }
2959 
2960 /**
2961  * totem_object_remote_set_setting:
2962  * @totem: a #TotemObject
2963  * @setting: a #TotemRemoteSetting
2964  * @value: the new value for the setting
2965  *
2966  * Sets @setting to @value on this instance of Totem.
2967  **/
totem_object_remote_set_setting(TotemObject * totem,TotemRemoteSetting setting,gboolean value)2968 void totem_object_remote_set_setting (TotemObject *totem,
2969 				      TotemRemoteSetting setting,
2970 				      gboolean value)
2971 {
2972 	GAction *action;
2973 
2974 	switch (setting) {
2975 	case TOTEM_REMOTE_SETTING_REPEAT:
2976 		action = g_action_map_lookup_action (G_ACTION_MAP (totem), "repeat");
2977 		break;
2978 	default:
2979 		g_assert_not_reached ();
2980 	}
2981 
2982 	g_simple_action_set_state (G_SIMPLE_ACTION (action),
2983 				   g_variant_new_boolean (value));
2984 }
2985 
2986 /**
2987  * totem_object_remote_get_setting:
2988  * @totem: a #TotemObject
2989  * @setting: a #TotemRemoteSetting
2990  *
2991  * Returns the value of @setting for this instance of Totem.
2992  *
2993  * Return value: %TRUE if the setting is enabled, %FALSE otherwise
2994  **/
2995 gboolean
totem_object_remote_get_setting(TotemObject * totem,TotemRemoteSetting setting)2996 totem_object_remote_get_setting (TotemObject        *totem,
2997 				 TotemRemoteSetting  setting)
2998 {
2999 	GAction *action;
3000 	GVariant *v;
3001 	gboolean ret;
3002 
3003 	action = NULL;
3004 
3005 	switch (setting) {
3006 	case TOTEM_REMOTE_SETTING_REPEAT:
3007 		action = g_action_map_lookup_action (G_ACTION_MAP (totem), "repeat");
3008 		break;
3009 	default:
3010 		g_assert_not_reached ();
3011 	}
3012 
3013 	v = g_action_get_state (action);
3014 	ret = g_variant_get_boolean (v);
3015 	g_variant_unref (v);
3016 
3017 	return ret;
3018 }
3019 
3020 static void
playlist_changed_cb(GtkWidget * playlist,TotemObject * totem)3021 playlist_changed_cb (GtkWidget *playlist, TotemObject *totem)
3022 {
3023 	char *mrl, *subtitle;
3024 
3025 	update_buttons (totem);
3026 	mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
3027 
3028 	if (mrl == NULL)
3029 		return;
3030 
3031 	if (totem_playlist_get_playing (totem->playlist) == TOTEM_PLAYLIST_STATUS_NONE) {
3032 		if (totem->pause_start)
3033 			totem_object_set_mrl (totem, mrl, subtitle);
3034 		else
3035 			totem_object_set_mrl_and_play (totem, mrl, subtitle);
3036 	}
3037 
3038 	totem->pause_start = FALSE;
3039 
3040 	g_free (mrl);
3041 	g_free (subtitle);
3042 }
3043 
3044 static void
item_activated_cb(GtkWidget * playlist,TotemObject * totem)3045 item_activated_cb (GtkWidget *playlist, TotemObject *totem)
3046 {
3047 	totem_object_seek (totem, 0);
3048 }
3049 
3050 static void
current_removed_cb(GtkWidget * playlist,TotemObject * totem)3051 current_removed_cb (GtkWidget *playlist, TotemObject *totem)
3052 {
3053 	char *mrl, *subtitle;
3054 
3055 	/* Set play button status */
3056 	play_pause_set_label (totem, STATE_STOPPED);
3057 	mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
3058 
3059 	if (mrl == NULL) {
3060 		g_free (subtitle);
3061 		subtitle = NULL;
3062 		totem_playlist_set_at_start (totem->playlist);
3063 		update_buttons (totem);
3064 		mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
3065 	} else {
3066 		update_buttons (totem);
3067 	}
3068 
3069 	totem_object_set_mrl_and_play (totem, mrl, subtitle);
3070 	g_free (mrl);
3071 	g_free (subtitle);
3072 }
3073 
3074 static void
subtitle_changed_cb(GtkWidget * playlist,TotemObject * totem)3075 subtitle_changed_cb (GtkWidget *playlist, TotemObject *totem)
3076 {
3077 	char *mrl, *subtitle;
3078 
3079 	mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
3080 	bacon_video_widget_set_text_subtitle (totem->bvw, subtitle);
3081 
3082 	g_free (mrl);
3083 	g_free (subtitle);
3084 }
3085 
3086 static void
playlist_repeat_toggle_cb(TotemPlaylist * playlist,GParamSpec * pspec,TotemObject * totem)3087 playlist_repeat_toggle_cb (TotemPlaylist *playlist, GParamSpec *pspec, TotemObject *totem)
3088 {
3089 	GAction *action;
3090 	gboolean repeat;
3091 
3092 	repeat = totem_playlist_get_repeat (playlist);
3093 	action = g_action_map_lookup_action (G_ACTION_MAP (totem), "repeat");
3094 	g_simple_action_set_state (G_SIMPLE_ACTION (action),
3095 				   g_variant_new_boolean (repeat));
3096 }
3097 
3098 /**
3099  * totem_object_is_fullscreen:
3100  * @totem: a #TotemObject
3101  *
3102  * Returns %TRUE if Totem is fullscreened.
3103  *
3104  * Return value: %TRUE if Totem is fullscreened
3105  **/
3106 gboolean
totem_object_is_fullscreen(TotemObject * totem)3107 totem_object_is_fullscreen (TotemObject *totem)
3108 {
3109 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), FALSE);
3110 
3111 	return (totem->controls_visibility == TOTEM_CONTROLS_FULLSCREEN);
3112 }
3113 
3114 /**
3115  * totem_object_is_playing:
3116  * @totem: a #TotemObject
3117  *
3118  * Returns %TRUE if Totem is playing a stream.
3119  *
3120  * Return value: %TRUE if Totem is playing a stream
3121  **/
3122 gboolean
totem_object_is_playing(TotemObject * totem)3123 totem_object_is_playing (TotemObject *totem)
3124 {
3125 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), FALSE);
3126 
3127 	if (totem->bvw == NULL)
3128 		return FALSE;
3129 
3130 	return bacon_video_widget_is_playing (totem->bvw) != FALSE;
3131 }
3132 
3133 /**
3134  * totem_object_is_paused:
3135  * @totem: a #TotemObject
3136  *
3137  * Returns %TRUE if playback is paused.
3138  *
3139  * Return value: %TRUE if playback is paused, %FALSE otherwise
3140  **/
3141 gboolean
totem_object_is_paused(TotemObject * totem)3142 totem_object_is_paused (TotemObject *totem)
3143 {
3144 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), FALSE);
3145 
3146 	return totem->state == STATE_PAUSED;
3147 }
3148 
3149 /**
3150  * totem_object_is_seekable:
3151  * @totem: a #TotemObject
3152  *
3153  * Returns %TRUE if the current stream is seekable.
3154  *
3155  * Return value: %TRUE if the current stream is seekable
3156  **/
3157 gboolean
totem_object_is_seekable(TotemObject * totem)3158 totem_object_is_seekable (TotemObject *totem)
3159 {
3160 	g_return_val_if_fail (TOTEM_IS_OBJECT (totem), FALSE);
3161 
3162 	if (totem->bvw == NULL)
3163 		return FALSE;
3164 
3165 	return bacon_video_widget_is_seekable (totem->bvw) != FALSE;
3166 }
3167 
3168 static gboolean
event_is_touch(GdkEventButton * event)3169 event_is_touch (GdkEventButton *event)
3170 {
3171 	GdkDevice *device;
3172 
3173 	device = gdk_event_get_device ((GdkEvent *) event);
3174 	return (gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN);
3175 }
3176 
3177 static gboolean
on_video_button_press_event(BaconVideoWidget * bvw,GdkEventButton * event,TotemObject * totem)3178 on_video_button_press_event (BaconVideoWidget *bvw, GdkEventButton *event,
3179 		TotemObject *totem)
3180 {
3181 	if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
3182 		gtk_widget_grab_focus (GTK_WIDGET (bvw));
3183 		return TRUE;
3184 	} else if (event->type == GDK_2BUTTON_PRESS &&
3185 		   event->button == 1 &&
3186 		   event_is_touch (event) == FALSE) {
3187 		totem_object_action_fullscreen_toggle (totem);
3188 		return TRUE;
3189 	} else if (event->type == GDK_BUTTON_PRESS && event->button == 2) {
3190 		totem_object_play_pause (totem);
3191 		return TRUE;
3192 	}
3193 
3194 	return FALSE;
3195 }
3196 
3197 static gboolean
on_eos_event(GtkWidget * widget,TotemObject * totem)3198 on_eos_event (GtkWidget *widget, TotemObject *totem)
3199 {
3200 	reset_seek_status (totem);
3201 
3202 	if (bacon_video_widget_get_logo_mode (totem->bvw) != FALSE)
3203 		return FALSE;
3204 
3205 	if (totem_playlist_has_next_mrl (totem->playlist) == FALSE &&
3206 	    totem_playlist_get_repeat (totem->playlist) == FALSE &&
3207 	    (totem_playlist_get_last (totem->playlist) != 0 ||
3208 	     totem_object_is_seekable (totem) == FALSE)) {
3209 		char *mrl, *subtitle;
3210 
3211 		/* Set play button status */
3212 		totem_playlist_set_at_start (totem->playlist);
3213 		update_buttons (totem);
3214 		bacon_video_widget_stop (totem->bvw);
3215 		play_pause_set_label (totem, STATE_STOPPED);
3216 		mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
3217 		totem_object_set_mrl (totem, mrl, subtitle);
3218 		bacon_video_widget_pause (totem->bvw);
3219 		g_free (mrl);
3220 		g_free (subtitle);
3221 	} else {
3222 		if (totem_playlist_get_last (totem->playlist) == 0 &&
3223 		    totem_object_is_seekable (totem)) {
3224 			if (totem_playlist_get_repeat (totem->playlist) != FALSE) {
3225 				totem_object_seek_time (totem, 0, FALSE);
3226 				totem_object_play (totem);
3227 			} else {
3228 				totem_object_pause (totem);
3229 				totem_object_seek_time (totem, 0, FALSE);
3230 			}
3231 		} else {
3232 			totem_object_seek_next (totem);
3233 		}
3234 	}
3235 
3236 	return FALSE;
3237 }
3238 
3239 static void
totem_object_handle_seek(TotemObject * totem,GdkEventKey * event,gboolean is_forward)3240 totem_object_handle_seek (TotemObject *totem, GdkEventKey *event, gboolean is_forward)
3241 {
3242 	if (is_forward != FALSE) {
3243 		if (event->state & GDK_SHIFT_MASK)
3244 			totem_object_seek_relative (totem, SEEK_FORWARD_SHORT_OFFSET * 1000, FALSE);
3245 		else if (event->state & GDK_CONTROL_MASK)
3246 			totem_object_seek_relative (totem, SEEK_FORWARD_LONG_OFFSET * 1000, FALSE);
3247 		else
3248 			totem_object_seek_relative (totem, SEEK_FORWARD_OFFSET * 1000, FALSE);
3249 	} else {
3250 		if (event->state & GDK_SHIFT_MASK)
3251 			totem_object_seek_relative (totem, SEEK_BACKWARD_SHORT_OFFSET * 1000, FALSE);
3252 		else if (event->state & GDK_CONTROL_MASK)
3253 			totem_object_seek_relative (totem, SEEK_BACKWARD_LONG_OFFSET * 1000, FALSE);
3254 		else
3255 			totem_object_seek_relative (totem, SEEK_BACKWARD_OFFSET * 1000, FALSE);
3256 	}
3257 }
3258 
3259 static gboolean
totem_object_handle_key_press(TotemObject * totem,GdkEventKey * event)3260 totem_object_handle_key_press (TotemObject *totem, GdkEventKey *event)
3261 {
3262 	GdkModifierType mask;
3263 	gboolean retval;
3264 	gboolean switch_rtl = FALSE;
3265 
3266 	retval = TRUE;
3267 
3268 	mask = event->state & gtk_accelerator_get_default_mod_mask ();
3269 
3270 	switch (event->keyval) {
3271 	case GDK_KEY_A:
3272 	case GDK_KEY_a:
3273 		totem_object_toggle_aspect_ratio (totem);
3274 		break;
3275 	case GDK_KEY_AudioCycleTrack:
3276 		bacon_video_widget_set_next_language (totem->bvw);
3277 		break;
3278 	case GDK_KEY_AudioPrev:
3279 	case GDK_KEY_Back:
3280 	case GDK_KEY_B:
3281 	case GDK_KEY_b:
3282 		totem_object_seek_previous (totem);
3283 		bacon_video_widget_show_popup (totem->bvw);
3284 		break;
3285 	case GDK_KEY_C:
3286 	case GDK_KEY_c:
3287 		bacon_video_widget_dvd_event (totem->bvw,
3288 				BVW_DVD_CHAPTER_MENU);
3289 		break;
3290 	case GDK_KEY_F1:
3291 		totem_object_show_help (totem);
3292 		break;
3293 	case GDK_KEY_F5:
3294 		/* Start presentation button */
3295 		totem_object_set_fullscreen (totem, TRUE);
3296 		totem_object_play_pause (totem);
3297 		break;
3298 	case GDK_KEY_F11:
3299 	case GDK_KEY_f:
3300 	case GDK_KEY_F:
3301 		totem_object_action_fullscreen_toggle (totem);
3302 		break;
3303 	case GDK_KEY_CycleAngle:
3304 	case GDK_KEY_g:
3305 	case GDK_KEY_G:
3306 		totem_object_next_angle (totem);
3307 		break;
3308 	case GDK_KEY_H:
3309 	case GDK_KEY_h:
3310 		totem_object_show_keyboard_shortcuts (totem);
3311 		break;
3312 	case GDK_KEY_question:
3313 		totem_object_show_keyboard_shortcuts (totem);
3314 		break;
3315 	case GDK_KEY_M:
3316 	case GDK_KEY_m:
3317 		bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU);
3318 		break;
3319 	case GDK_KEY_AudioNext:
3320 	case GDK_KEY_Forward:
3321 	case GDK_KEY_N:
3322 	case GDK_KEY_n:
3323 	case GDK_KEY_End:
3324 		totem_object_seek_next (totem);
3325 		bacon_video_widget_show_popup (totem->bvw);
3326 		break;
3327 	case GDK_KEY_OpenURL:
3328 		totem_object_set_fullscreen (totem, FALSE);
3329 		totem_object_open_location (totem);
3330 		break;
3331 	case GDK_KEY_O:
3332 	case GDK_KEY_o:
3333 	case GDK_KEY_Open:
3334 		totem_object_set_fullscreen (totem, FALSE);
3335 		totem_object_open (totem);
3336 		break;
3337 	case GDK_KEY_AudioPlay:
3338 	case GDK_KEY_p:
3339 	case GDK_KEY_P:
3340 		totem_object_play_pause (totem);
3341 		break;
3342 	case GDK_KEY_comma:
3343 	case GDK_KEY_FrameBack:
3344 		totem_object_pause (totem);
3345 		bacon_video_widget_step (totem->bvw, FALSE, NULL);
3346 		break;
3347 	case GDK_KEY_period:
3348 	case GDK_KEY_FrameForward:
3349 		totem_object_pause (totem);
3350 		bacon_video_widget_step (totem->bvw, TRUE, NULL);
3351 		break;
3352 	case GDK_KEY_AudioPause:
3353 	case GDK_KEY_Pause:
3354 	case GDK_KEY_AudioStop:
3355 		totem_object_pause (totem);
3356 		break;
3357 	case GDK_KEY_q:
3358 	case GDK_KEY_Q:
3359 		totem_object_exit (totem);
3360 		break;
3361 	case GDK_KEY_r:
3362 	case GDK_KEY_R:
3363 	case GDK_KEY_ZoomIn:
3364 		totem_object_set_zoom (totem, TRUE);
3365 		break;
3366 	case GDK_KEY_Subtitle:
3367 		bacon_video_widget_set_next_subtitle (totem->bvw);
3368 		break;
3369 	case GDK_KEY_t:
3370 	case GDK_KEY_T:
3371 	case GDK_KEY_ZoomOut:
3372 		totem_object_set_zoom (totem, FALSE);
3373 		break;
3374 	case GDK_KEY_Eject:
3375 		totem_object_eject (totem);
3376 		break;
3377 	case GDK_KEY_Escape:
3378 		if (mask == GDK_SUPER_MASK)
3379 			bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU);
3380 		else
3381 			totem_object_set_fullscreen (totem, FALSE);
3382 		break;
3383 	case GDK_KEY_space:
3384 	case GDK_KEY_Return:
3385 		if (mask != GDK_CONTROL_MASK) {
3386 			GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (totem->win));
3387 			if (totem_object_is_fullscreen (totem) != FALSE || focus == NULL ||
3388 			    focus == GTK_WIDGET (totem->bvw) || focus == totem->seek) {
3389 				if (event->keyval == GDK_KEY_space) {
3390 					totem_object_play_pause (totem);
3391 				} else if (bacon_video_widget_has_menus (totem->bvw) != FALSE) {
3392 					bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU_SELECT);
3393 				}
3394 			} else
3395 				retval = FALSE;
3396 		} else {
3397 			if (event->keyval == GDK_KEY_space)
3398 				totem_object_play_pause (totem);
3399 		}
3400 		break;
3401 	case GDK_KEY_Left:
3402 	case GDK_KEY_Right:
3403 		if (event->state & GDK_MOD1_MASK) {
3404 			gboolean is_forward;
3405 
3406 			is_forward = (event->keyval == GDK_KEY_Right);
3407 			/* Switch direction in RTL environment */
3408 			if (gtk_widget_get_direction (totem->win) == GTK_TEXT_DIR_RTL)
3409 				is_forward = !is_forward;
3410 			if (is_forward)
3411 				totem_object_seek_next (totem);
3412 			else
3413 				totem_object_seek_previous (totem);
3414 			break;
3415 		}
3416 		switch_rtl = TRUE;
3417 		/* fall through */
3418 	case GDK_KEY_Page_Up:
3419 	case GDK_KEY_Page_Down:
3420 		if (bacon_video_widget_has_menus (totem->bvw) == FALSE) {
3421 			gboolean is_forward;
3422 
3423 			is_forward = (event->keyval == GDK_KEY_Right || event->keyval == GDK_KEY_Page_Up);
3424 			/* Switch direction in RTL environment */
3425 			if (switch_rtl && gtk_widget_get_direction (totem->win) == GTK_TEXT_DIR_RTL)
3426 				is_forward = !is_forward;
3427 
3428 			if (totem_object_is_seekable (totem)) {
3429 				totem_object_handle_seek (totem, event, is_forward);
3430 				bacon_video_widget_show_popup (totem->bvw);
3431 			}
3432 		} else {
3433 			if (event->keyval == GDK_KEY_Left || event->keyval == GDK_KEY_Page_Down)
3434 				bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU_LEFT);
3435 			else
3436 				bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU_RIGHT);
3437 		}
3438 		break;
3439 	case GDK_KEY_Home:
3440 		totem_object_seek (totem, 0);
3441 		bacon_video_widget_show_popup (totem->bvw);
3442 		break;
3443 	case GDK_KEY_Up:
3444 		if (bacon_video_widget_has_menus (totem->bvw) != FALSE)
3445 			bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU_UP);
3446 		else if (mask == GDK_SHIFT_MASK)
3447 			totem_object_set_volume_relative (totem, VOLUME_UP_SHORT_OFFSET);
3448 		else
3449 			totem_object_set_volume_relative (totem, VOLUME_UP_OFFSET);
3450 		break;
3451 	case GDK_KEY_Down:
3452 		if (bacon_video_widget_has_menus (totem->bvw) != FALSE)
3453 			bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU_DOWN);
3454 		else if (mask == GDK_SHIFT_MASK)
3455 			totem_object_set_volume_relative (totem, VOLUME_DOWN_SHORT_OFFSET);
3456 		else
3457 			totem_object_set_volume_relative (totem, VOLUME_DOWN_OFFSET);
3458 		break;
3459 	case GDK_KEY_Select:
3460 		if (bacon_video_widget_has_menus (totem->bvw) != FALSE)
3461 			bacon_video_widget_dvd_event (totem->bvw, BVW_DVD_ROOT_MENU_SELECT);
3462 		break;
3463 	case GDK_KEY_0:
3464 		if (mask == GDK_CONTROL_MASK)
3465 			totem_object_set_zoom (totem, FALSE);
3466 		break;
3467 	case GDK_KEY_Menu:
3468 	case GDK_KEY_F10:
3469 		bacon_video_widget_show_popup (totem->bvw);
3470 		if (totem->controls_visibility != TOTEM_CONTROLS_FULLSCREEN) {
3471 			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (totem->gear_button),
3472 						      !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (totem->gear_button)));
3473 		} else {
3474 			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (totem->fullscreen_gear_button),
3475 						      !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (totem->fullscreen_gear_button)));
3476 		}
3477 		break;
3478 	case GDK_KEY_Time:
3479 		bacon_video_widget_show_popup (totem->bvw);
3480 		break;
3481 	case GDK_KEY_equal:
3482 		if (mask == GDK_CONTROL_MASK)
3483 			totem_object_set_zoom (totem, TRUE);
3484 		break;
3485 	case GDK_KEY_hyphen:
3486 		if (mask == GDK_CONTROL_MASK)
3487 			totem_object_set_zoom (totem, FALSE);
3488 		break;
3489 	case GDK_KEY_plus:
3490 	case GDK_KEY_KP_Add:
3491 		if (mask != GDK_CONTROL_MASK) {
3492 			totem_object_seek_next (totem);
3493 			bacon_video_widget_show_popup (totem->bvw);
3494 		} else {
3495 			totem_object_set_zoom (totem, TRUE);
3496 		}
3497 		break;
3498 	case GDK_KEY_minus:
3499 	case GDK_KEY_KP_Subtract:
3500 		if (mask != GDK_CONTROL_MASK) {
3501 			totem_object_seek_previous (totem);
3502 			bacon_video_widget_show_popup (totem->bvw);
3503 		} else {
3504 			totem_object_set_zoom (totem, FALSE);
3505 		}
3506 		break;
3507 	case GDK_KEY_KP_Up:
3508 	case GDK_KEY_KP_8:
3509 		bacon_video_widget_dvd_event (totem->bvw,
3510 					      BVW_DVD_ROOT_MENU_UP);
3511 		break;
3512 	case GDK_KEY_KP_Down:
3513 	case GDK_KEY_KP_2:
3514 		bacon_video_widget_dvd_event (totem->bvw,
3515 					      BVW_DVD_ROOT_MENU_DOWN);
3516 		break;
3517 	case GDK_KEY_KP_Right:
3518 	case GDK_KEY_KP_6:
3519 		bacon_video_widget_dvd_event (totem->bvw,
3520 					      BVW_DVD_ROOT_MENU_RIGHT);
3521 		break;
3522 	case GDK_KEY_KP_Left:
3523 	case GDK_KEY_KP_4:
3524 		bacon_video_widget_dvd_event (totem->bvw,
3525 					      BVW_DVD_ROOT_MENU_LEFT);
3526 		break;
3527 	case GDK_KEY_KP_Begin:
3528 	case GDK_KEY_KP_5:
3529 		bacon_video_widget_dvd_event (totem->bvw,
3530 					      BVW_DVD_ROOT_MENU_SELECT);
3531 	default:
3532 		retval = FALSE;
3533 	}
3534 
3535 	return retval;
3536 }
3537 
3538 static void
on_seek_requested_event(BaconVideoWidget * bvw,gboolean forward,TotemObject * totem)3539 on_seek_requested_event (BaconVideoWidget *bvw,
3540 			 gboolean          forward,
3541 			 TotemObject      *totem)
3542 {
3543 	gint64 offset;
3544 
3545 	offset = forward ? SEEK_FORWARD_OFFSET * 1000 : SEEK_BACKWARD_OFFSET * 1000;
3546 	totem_object_seek_relative (totem, offset, FALSE);
3547 }
3548 
3549 static void
on_track_skip_requested_event(BaconVideoWidget * bvw,gboolean is_forward,TotemObject * totem)3550 on_track_skip_requested_event (BaconVideoWidget *bvw,
3551 			       gboolean          is_forward,
3552 			       TotemObject      *totem)
3553 {
3554 	totem_object_direction (totem, is_forward ? TOTEM_PLAYLIST_DIRECTION_NEXT : TOTEM_PLAYLIST_DIRECTION_PREVIOUS);
3555 }
3556 
3557 static void
on_volume_change_requested_event(BaconVideoWidget * bvw,gboolean increase,TotemObject * totem)3558 on_volume_change_requested_event (BaconVideoWidget *bvw,
3559 				  gboolean          increase,
3560 				  TotemObject      *totem)
3561 {
3562 	totem_object_set_volume_relative (totem, increase ? VOLUME_UP_OFFSET : VOLUME_DOWN_OFFSET);
3563 }
3564 
3565 gboolean
window_key_press_event_cb(GtkWidget * win,GdkEventKey * event,TotemObject * totem)3566 window_key_press_event_cb (GtkWidget *win, GdkEventKey *event, TotemObject *totem)
3567 {
3568 	/* Shortcuts disabled? */
3569 	if (totem->disable_kbd_shortcuts != FALSE)
3570 		return FALSE;
3571 
3572 	/* Handle Quit */
3573 	if ((event->state & GDK_CONTROL_MASK) &&
3574 	    event->type == GDK_KEY_PRESS &&
3575 	    (event->keyval == GDK_KEY_Q ||
3576 	     event->keyval == GDK_KEY_q)) {
3577 		return totem_object_handle_key_press (totem, event);
3578 	}
3579 
3580 	/* Handle back/quit */
3581 	if ((event->state & GDK_CONTROL_MASK) &&
3582 	    event->type == GDK_KEY_PRESS &&
3583 	    (event->keyval == GDK_KEY_W ||
3584 	     event->keyval == GDK_KEY_w)) {
3585 		if (totem_grilo_get_show_back_button (TOTEM_GRILO (totem->grilo)) ||
3586 		    g_str_equal (totem_object_get_main_page (totem), "player"))
3587 			back_button_clicked_cb (NULL, totem);
3588 		else
3589 			totem_object_exit (totem);
3590 		return FALSE;
3591 	}
3592 
3593 	/* Check whether we're in the player panel */
3594 	if (!g_str_equal (totem_object_get_main_page (totem), "player")) {
3595 		if (event->type == GDK_KEY_PRESS &&
3596 		    event->keyval == GDK_KEY_Back &&
3597 		    totem_grilo_get_show_back_button (TOTEM_GRILO (totem->grilo)))
3598 			back_button_clicked_cb (NULL, totem);
3599 		return FALSE;
3600 	}
3601 
3602 	/* Special case Eject, Open, Open URI,
3603 	 * seeking and zoom keyboard shortcuts */
3604 	if (event->state != 0 && (event->state & GDK_CONTROL_MASK))
3605 	{
3606 		switch (event->keyval) {
3607 		case GDK_KEY_E:
3608 		case GDK_KEY_e:
3609 		case GDK_KEY_f:
3610 		case GDK_KEY_F:
3611 		case GDK_KEY_O:
3612 		case GDK_KEY_o:
3613 		case GDK_KEY_L:
3614 		case GDK_KEY_l:
3615 		case GDK_KEY_q:
3616 		case GDK_KEY_Q:
3617 		case GDK_KEY_space:
3618 		case GDK_KEY_Right:
3619 		case GDK_KEY_Left:
3620 		case GDK_KEY_plus:
3621 		case GDK_KEY_KP_Add:
3622 		case GDK_KEY_minus:
3623 		case GDK_KEY_KP_Subtract:
3624 		case GDK_KEY_0:
3625 		case GDK_KEY_equal:
3626 		case GDK_KEY_hyphen:
3627 			if (event->type == GDK_KEY_PRESS)
3628 				return totem_object_handle_key_press (totem, event);
3629 		default:
3630 			break;
3631 		}
3632 	}
3633 
3634 	if (event->state != 0 && (event->state & GDK_SUPER_MASK)) {
3635 		switch (event->keyval) {
3636 		case GDK_KEY_Escape:
3637 			if (event->type == GDK_KEY_PRESS)
3638 				return totem_object_handle_key_press (totem, event);
3639 		default:
3640 			break;
3641 		}
3642 	}
3643 
3644 	if (event->state != 0 && (event->state & GDK_MOD1_MASK)) {
3645 		switch (event->keyval) {
3646 		case GDK_KEY_Left:
3647 		case GDK_KEY_Right:
3648 			if (event->type == GDK_KEY_PRESS)
3649 				return totem_object_handle_key_press (totem, event);
3650 		default:
3651 			break;
3652 		}
3653 
3654 	}
3655 
3656 	/* If we have modifiers, and either Ctrl, Mod1 (Alt), or any
3657 	 * of Mod3 to Mod5 (Mod2 is num-lock...) are pressed, we
3658 	 * let Gtk+ handle the key */
3659 	if (event->state != 0 &&
3660 	    ((event->state & GDK_CONTROL_MASK) ||
3661 	     (event->state & GDK_MOD1_MASK) ||
3662 	     (event->state & GDK_MOD3_MASK) ||
3663 	     (event->state & GDK_MOD4_MASK)))
3664 		return FALSE;
3665 
3666 	if (event->type == GDK_KEY_PRESS)
3667 		return totem_object_handle_key_press (totem, event);
3668 
3669 	return FALSE;
3670 }
3671 
3672 static void
update_media_menu_items(TotemObject * totem)3673 update_media_menu_items (TotemObject *totem)
3674 {
3675 	GMount *mount;
3676 	gboolean playing;
3677 
3678 	playing = totem_playing_dvd (totem->mrl);
3679 
3680 	totem_object_set_sensitivity2 ("dvd-root-menu", playing);
3681 	totem_object_set_sensitivity2 ("dvd-title-menu", playing);
3682 	totem_object_set_sensitivity2 ("dvd-audio-menu", playing);
3683 	totem_object_set_sensitivity2 ("dvd-angle-menu", playing);
3684 	totem_object_set_sensitivity2 ("dvd-chapter-menu", playing);
3685 
3686 	totem_object_set_sensitivity2 ("next-angle",
3687 				       bacon_video_widget_has_angles (totem->bvw));
3688 
3689 	mount = totem_get_mount_for_media (totem->mrl);
3690 	totem_object_set_sensitivity2 ("eject", mount != NULL);
3691 	if (mount != NULL)
3692 		g_object_unref (mount);
3693 }
3694 
3695 static void
update_buttons(TotemObject * totem)3696 update_buttons (TotemObject *totem)
3697 {
3698 	totem_object_set_sensitivity2 ("previous-chapter",
3699 				       totem_object_can_seek_previous (totem));
3700 	totem_object_set_sensitivity2 ("next-chapter",
3701 				       totem_object_can_seek_next (totem));
3702 }
3703 
3704 void
totem_setup_window(TotemObject * totem)3705 totem_setup_window (TotemObject *totem)
3706 {
3707 	GKeyFile *keyfile;
3708 	int w, h;
3709 	char *filename;
3710 	GError *err = NULL;
3711 	GtkWidget *vbox;
3712 	GdkRGBA black;
3713 
3714 	filename = g_build_filename (totem_dot_dir (), "state.ini", NULL);
3715 	keyfile = g_key_file_new ();
3716 	if (g_key_file_load_from_file (keyfile, filename,
3717 			G_KEY_FILE_NONE, NULL) == FALSE) {
3718 		w = DEFAULT_WINDOW_W;
3719 		h = DEFAULT_WINDOW_H;
3720 		totem->maximised = TRUE;
3721 		g_free (filename);
3722 	} else {
3723 		g_free (filename);
3724 
3725 		w = g_key_file_get_integer (keyfile, "State", "window_w", &err);
3726 		if (err != NULL) {
3727 			w = 0;
3728 			g_error_free (err);
3729 			err = NULL;
3730 		}
3731 
3732 		h = g_key_file_get_integer (keyfile, "State", "window_h", &err);
3733 		if (err != NULL) {
3734 			h = 0;
3735 			g_error_free (err);
3736 			err = NULL;
3737 		}
3738 
3739 		totem->maximised = g_key_file_get_boolean (keyfile, "State",
3740 				"maximised", &err);
3741 		if (err != NULL) {
3742 			g_error_free (err);
3743 			err = NULL;
3744 		}
3745 	}
3746 
3747 	if (w > 0 && h > 0 && totem->maximised == FALSE) {
3748 		gtk_window_set_default_size (GTK_WINDOW (totem->win),
3749 				w, h);
3750 		totem->window_w = w;
3751 		totem->window_h = h;
3752 	} else if (totem->maximised != FALSE) {
3753 		gtk_window_maximize (GTK_WINDOW (totem->win));
3754 	}
3755 
3756 	/* Set the vbox to be completely black */
3757 	vbox = GTK_WIDGET (gtk_builder_get_object (totem->xml, "tmw_bvw_box"));
3758 	gdk_rgba_parse (&black, "Black");
3759 	gtk_widget_override_background_color (vbox, (GTK_STATE_FLAG_FOCUSED << 1), &black);
3760 
3761 	/* Headerbar */
3762 	totem->header = g_object_new (TOTEM_TYPE_MAIN_TOOLBAR,
3763 				      "show-search-button", TRUE,
3764 				      "show-select-button", TRUE,
3765 				      "show-close-button", TRUE,
3766 				      "title", _("Videos"),
3767 				      NULL);
3768 	g_signal_connect (G_OBJECT (totem->header), "back-clicked",
3769 			  G_CALLBACK (back_button_clicked_cb), totem);
3770 	gtk_window_set_titlebar (GTK_WINDOW (totem->win), totem->header);
3771 
3772 	return;
3773 }
3774 
3775 static void
popup_menu_shown_cb(GtkToggleButton * button,TotemObject * totem)3776 popup_menu_shown_cb (GtkToggleButton *button,
3777 		     TotemObject     *totem)
3778 {
3779 	if (gtk_toggle_button_get_active (button))
3780 		bacon_video_widget_mark_popup_busy (totem->bvw, "toolbar/go menu visible");
3781 	else
3782 		bacon_video_widget_unmark_popup_busy (totem->bvw, "toolbar/go menu visible");
3783 }
3784 
3785 static void
volume_button_menu_shown_cb(GObject * popover,GParamSpec * pspec,TotemObject * totem)3786 volume_button_menu_shown_cb (GObject     *popover,
3787 			     GParamSpec  *pspec,
3788 			     TotemObject *totem)
3789 {
3790 	if (gtk_widget_is_visible (GTK_WIDGET (popover)))
3791 		bacon_video_widget_mark_popup_busy (totem->bvw, "volume menu visible");
3792 	else
3793 		bacon_video_widget_unmark_popup_busy (totem->bvw, "volume menu visible");
3794 }
3795 
3796 static void
update_add_button_visibility(GObject * gobject,GParamSpec * pspec,TotemObject * totem)3797 update_add_button_visibility (GObject     *gobject,
3798 			      GParamSpec  *pspec,
3799 			      TotemObject *totem)
3800 {
3801 	TotemMainToolbar *bar = TOTEM_MAIN_TOOLBAR (gobject);
3802 
3803 	if (totem_main_toolbar_get_search_mode (bar) ||
3804 	    totem_main_toolbar_get_select_mode (bar)) {
3805 		gtk_widget_hide (totem->add_button);
3806 	} else {
3807 		gtk_widget_set_visible (totem->add_button,
3808 					totem_grilo_get_current_page (TOTEM_GRILO (totem->grilo))  == TOTEM_GRILO_PAGE_RECENT);
3809 	}
3810 }
3811 
3812 static GtkWidget *
create_control_button(TotemObject * totem,const gchar * action_name,const gchar * icon_name,const gchar * tooltip_text)3813 create_control_button (TotemObject *totem,
3814 		       const gchar *action_name,
3815 		       const gchar *icon_name,
3816 		       const gchar *tooltip_text)
3817 {
3818 	GtkWidget *button, *image;
3819 
3820 	button = gtk_button_new ();
3821 	gtk_actionable_set_action_name (GTK_ACTIONABLE (button), action_name);
3822 	image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
3823 	gtk_button_set_image (GTK_BUTTON (button), image);
3824 	gtk_widget_set_valign (GTK_WIDGET (button), GTK_ALIGN_CENTER);
3825 	gtk_style_context_add_class (gtk_widget_get_style_context (button), "image-button");
3826 	if (g_str_equal (action_name, "app.play")) {
3827 		g_object_set (G_OBJECT (image),
3828 			      "margin-start", 16,
3829 			      "margin-end", 16,
3830 			      NULL);
3831 		totem->play_button = button;
3832 	}
3833 
3834 	gtk_button_set_label (GTK_BUTTON (button), NULL);
3835 	gtk_widget_set_tooltip_text (button, tooltip_text);
3836 	atk_object_set_name (gtk_widget_get_accessible (button), tooltip_text);
3837 
3838 	gtk_widget_show_all (button);
3839 
3840 	return button;
3841 }
3842 
3843 void
totem_callback_connect(TotemObject * totem)3844 totem_callback_connect (TotemObject *totem)
3845 {
3846 	GtkWidget *item;
3847 	GtkBox *box;
3848 	GAction *gaction;
3849 	GMenuModel *menu;
3850 	GtkPopover *popover;
3851 
3852 	/* Menu items */
3853 	gaction = g_action_map_lookup_action (G_ACTION_MAP (totem), "repeat");
3854 	g_simple_action_set_state (G_SIMPLE_ACTION (gaction),
3855 				   g_variant_new_boolean (totem_playlist_get_repeat (totem->playlist)));
3856 
3857 	/* Controls */
3858 	box = g_object_get_data (totem->controls, "controls_box");
3859 	gtk_widget_insert_action_group (GTK_WIDGET (box), "app", G_ACTION_GROUP (totem));
3860 
3861 	/* Previous */
3862 	item = create_control_button (totem, "app.previous-chapter",
3863 				      "media-skip-backward-symbolic",
3864 				      _("Previous Chapter/Movie"));
3865 	gtk_box_pack_start (box, item, FALSE, FALSE, 0);
3866 
3867 	/* Play/Pause */
3868 	item = create_control_button (totem, "app.play",
3869 				      "media-playback-start-symbolic",
3870 				      _("Play / Pause"));
3871 	gtk_box_pack_start (box, item, FALSE, FALSE, 0);
3872 
3873 	/* Next */
3874 	item = create_control_button (totem, "app.next-chapter",
3875 				      "media-skip-forward-symbolic",
3876 				      _("Next Chapter/Movie"));
3877 	gtk_box_pack_start (box, item, FALSE, FALSE, 0);
3878 
3879 	/* Seekbar */
3880 	g_signal_connect (totem->seek, "button-press-event",
3881 			  G_CALLBACK (seek_slider_pressed_cb), totem);
3882 	g_signal_connect (totem->seek, "button-release-event",
3883 			  G_CALLBACK (seek_slider_released_cb), totem);
3884 	g_signal_connect (totem->seekadj, "value-changed",
3885 			  G_CALLBACK (seek_slider_changed_cb), totem);
3886 
3887 	/* Volume */
3888 	g_signal_connect (totem->volume, "value-changed",
3889 			  G_CALLBACK (volume_button_value_changed_cb), totem);
3890 	item = gtk_scale_button_get_popup (GTK_SCALE_BUTTON (totem->volume));
3891 	g_signal_connect (G_OBJECT (item), "notify::visible",
3892 			  G_CALLBACK (volume_button_menu_shown_cb), totem);
3893 
3894 	/* Go button */
3895 	item = g_object_get_data (totem->controls, "go_button");
3896 	menu = (GMenuModel *) gtk_builder_get_object (totem->xml, "gomenu");
3897 	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (item), menu);
3898 	popover = gtk_menu_button_get_popover (GTK_MENU_BUTTON (item));
3899 	gtk_popover_set_transitions_enabled (GTK_POPOVER (popover), FALSE);
3900 	gtk_widget_set_size_request (GTK_WIDGET (popover), 175, -1);
3901 	g_signal_connect (G_OBJECT (item), "toggled",
3902 			  G_CALLBACK (popup_menu_shown_cb), totem);
3903 
3904 	/* Main menu */
3905 	item = totem->main_menu_button = totem_interface_create_header_button (totem->header,
3906 									       gtk_menu_button_new (),
3907 									       "open-menu-symbolic",
3908 									       GTK_PACK_END);
3909 	gtk_container_child_set (GTK_CONTAINER (totem->header), totem->main_menu_button,
3910 				 "position", 0,
3911 				 NULL);
3912 	menu = (GMenuModel *) gtk_builder_get_object (totem->xml, "appmenu");
3913 	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (item), menu);
3914 	gtk_widget_show (item);
3915 
3916 	/* Player menu */
3917 	item = totem->gear_button = totem_interface_create_header_button (totem->header,
3918 									  gtk_menu_button_new (),
3919 									  "view-more-symbolic",
3920 									  GTK_PACK_END);
3921 	menu = (GMenuModel *) gtk_builder_get_object (totem->xml, "playermenu");
3922 	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (item), menu);
3923 	popover = gtk_menu_button_get_popover (GTK_MENU_BUTTON (item));
3924 	gtk_popover_set_transitions_enabled (GTK_POPOVER (popover), FALSE);
3925 	g_signal_connect (G_OBJECT (item), "toggled",
3926 			  G_CALLBACK (popup_menu_shown_cb), totem);
3927 
3928 	/* Add button */
3929 	item = totem->add_button = totem_interface_create_header_button (totem->header,
3930 									 gtk_menu_button_new (),
3931 									 "list-add-symbolic",
3932 									 GTK_PACK_START);
3933 	menu = (GMenuModel *) gtk_builder_get_object (totem->xml, "addmenu");
3934 	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (item), menu);
3935 	gtk_widget_show (item);
3936 
3937 	g_signal_connect (G_OBJECT (totem->header), "notify::search-mode",
3938 			  G_CALLBACK (update_add_button_visibility), totem);
3939 	g_signal_connect (G_OBJECT (totem->header), "notify::select-mode",
3940 			  G_CALLBACK (update_add_button_visibility), totem);
3941 
3942 	/* Fullscreen button */
3943 	item = totem->fullscreen_button = totem_interface_create_header_button (totem->header,
3944 										gtk_button_new (),
3945 										"view-fullscreen-symbolic",
3946 										GTK_PACK_END);
3947 	gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "app.fullscreen");
3948 
3949 	/* Connect the keys */
3950 	gtk_widget_add_events (totem->win, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
3951 
3952 	/* Set sensitivity of the toolbar buttons */
3953 	totem_object_set_sensitivity2 ("play", FALSE);
3954 	totem_object_set_sensitivity2 ("next-chapter", FALSE);
3955 	totem_object_set_sensitivity2 ("previous-chapter", FALSE);
3956 
3957 	/* Volume */
3958 	g_signal_connect (G_OBJECT (totem->bvw), "notify::volume",
3959 			G_CALLBACK (property_notify_cb_volume), totem);
3960 	g_signal_connect (G_OBJECT (totem->bvw), "notify::seekable",
3961 			G_CALLBACK (property_notify_cb_seekable), totem);
3962 	update_volume_sliders (totem);
3963 }
3964 
3965 void
playlist_widget_setup(TotemObject * totem)3966 playlist_widget_setup (TotemObject *totem)
3967 {
3968 	totem->playlist = TOTEM_PLAYLIST (totem_playlist_new ());
3969 
3970 	if (totem->playlist == NULL)
3971 		totem_object_exit (totem);
3972 
3973 #if 0
3974 	{
3975 		GtkWidget *window;
3976 
3977 		window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
3978 		gtk_window_set_default_size (GTK_WINDOW (window), 500, 400);
3979 		gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (totem->playlist));
3980 		gtk_widget_show_all (window);
3981 	}
3982 #endif
3983 	g_signal_connect (G_OBJECT (totem->playlist), "active-name-changed",
3984 			  G_CALLBACK (on_playlist_change_name), totem);
3985 	g_signal_connect (G_OBJECT (totem->playlist), "item-activated",
3986 			  G_CALLBACK (item_activated_cb), totem);
3987 	g_signal_connect (G_OBJECT (totem->playlist),
3988 			  "changed", G_CALLBACK (playlist_changed_cb),
3989 			  totem);
3990 	g_signal_connect (G_OBJECT (totem->playlist),
3991 			  "current-removed", G_CALLBACK (current_removed_cb),
3992 			  totem);
3993 	g_signal_connect (G_OBJECT (totem->playlist),
3994 			  "notify::repeat",
3995 			  G_CALLBACK (playlist_repeat_toggle_cb),
3996 			  totem);
3997 	g_signal_connect (G_OBJECT (totem->playlist),
3998 			  "subtitle-changed",
3999 			  G_CALLBACK (subtitle_changed_cb),
4000 			  totem);
4001 }
4002 
4003 static void
grilo_show_back_button_changed(TotemGrilo * grilo,GParamSpec * spec,TotemObject * totem)4004 grilo_show_back_button_changed (TotemGrilo  *grilo,
4005 				GParamSpec  *spec,
4006 				TotemObject *totem)
4007 {
4008 	if (g_strcmp0 (totem_object_get_main_page (totem), "grilo") == 0) {
4009 		g_object_set (totem->header,
4010 			      "show-back-button", totem_grilo_get_show_back_button (TOTEM_GRILO (totem->grilo)),
4011 			      NULL);
4012 	}
4013 }
4014 
4015 static void
grilo_current_page_changed(TotemGrilo * grilo,GParamSpec * spec,TotemObject * totem)4016 grilo_current_page_changed (TotemGrilo  *grilo,
4017 			    GParamSpec  *spec,
4018 			    TotemObject *totem)
4019 {
4020 	if (g_strcmp0 (totem_object_get_main_page (totem), "grilo") == 0) {
4021 		TotemGriloPage page;
4022 
4023 		page = totem_grilo_get_current_page (TOTEM_GRILO (totem->grilo));
4024 		gtk_widget_set_visible (totem->add_button,
4025 					page == TOTEM_GRILO_PAGE_RECENT);
4026 	}
4027 }
4028 
4029 void
grilo_widget_setup(TotemObject * totem)4030 grilo_widget_setup (TotemObject *totem)
4031 {
4032 	totem->grilo = totem_grilo_new (totem, totem->header);
4033 	g_signal_connect (G_OBJECT (totem->grilo), "notify::show-back-button",
4034 			  G_CALLBACK (grilo_show_back_button_changed), totem);
4035 	g_signal_connect (G_OBJECT (totem->grilo), "notify::current-page",
4036 			  G_CALLBACK (grilo_current_page_changed), totem);
4037 	gtk_stack_add_named (GTK_STACK (totem->stack),
4038 			     totem->grilo,
4039 			     "grilo");
4040 	gtk_stack_set_visible_child_name (GTK_STACK (totem->stack), "grilo");
4041 }
4042 
4043 static void
add_fullscreen_toolbar(TotemObject * totem)4044 add_fullscreen_toolbar (TotemObject *totem)
4045 {
4046 	GtkWidget *container, *item;
4047 	GMenuModel *menu;
4048 
4049 	container = GTK_WIDGET (bacon_video_widget_get_header_controls_object (totem->bvw));
4050 
4051 	totem->fullscreen_header = g_object_new (TOTEM_TYPE_MAIN_TOOLBAR,
4052 						 "show-search-button", FALSE,
4053 						 "show-select-button", FALSE,
4054 						 "show-back-button", TRUE,
4055 						 NULL);
4056 	g_object_bind_property (totem->header, "title",
4057 				totem->fullscreen_header, "title", 0);
4058 	g_object_bind_property (totem->header, "subtitle",
4059 				totem->fullscreen_header, "subtitle", 0);
4060 	g_signal_connect (G_OBJECT (totem->fullscreen_header), "back-clicked",
4061 			  G_CALLBACK (back_button_clicked_cb), totem);
4062 
4063 	item = totem_interface_create_header_button (totem->fullscreen_header,
4064 						     gtk_button_new (),
4065 						     "view-restore-symbolic",
4066 						     GTK_PACK_END);
4067 	gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "app.fullscreen");
4068 
4069 	item = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
4070 	gtk_header_bar_pack_end (GTK_HEADER_BAR (totem->fullscreen_header), item);
4071 	gtk_style_context_add_class (gtk_widget_get_style_context (item), "header-bar-separator");
4072 
4073 	item = totem_interface_create_header_button (totem->fullscreen_header,
4074 						     gtk_menu_button_new (),
4075 						     "view-more-symbolic",
4076 						     GTK_PACK_END);
4077 	menu = (GMenuModel *) gtk_builder_get_object (totem->xml, "playermenu");
4078 	gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (item), menu);
4079 	g_signal_connect (G_OBJECT (item), "toggled",
4080 			  G_CALLBACK (popup_menu_shown_cb), totem);
4081 	totem->fullscreen_gear_button = item;
4082 
4083 	gtk_container_add (GTK_CONTAINER (container), totem->fullscreen_header);
4084 	gtk_widget_show_all (totem->fullscreen_header);
4085 }
4086 
4087 void
video_widget_create(TotemObject * totem)4088 video_widget_create (TotemObject *totem)
4089 {
4090 	GError *err = NULL;
4091 	GtkContainer *container;
4092 	BaconVideoWidget **bvw;
4093 	GdkWindow *window;
4094 	gboolean fullscreen;
4095 
4096 	totem->bvw = BACON_VIDEO_WIDGET (bacon_video_widget_new (&err));
4097 
4098 	if (totem->bvw == NULL) {
4099 		totem_object_show_error_and_exit (_("Totem could not startup."), err != NULL ? err->message : _("No reason."), totem);
4100 		if (err != NULL)
4101 			g_error_free (err);
4102 	}
4103 
4104 	window = gtk_widget_get_window (totem->win);
4105 
4106 	fullscreen = window && ((gdk_window_get_state (window) & GDK_WINDOW_STATE_FULLSCREEN) != 0);
4107 	bacon_video_widget_set_fullscreen (totem->bvw, fullscreen);
4108 	totem->controls = bacon_video_widget_get_controls_object (totem->bvw);
4109 
4110 	g_signal_connect_after (G_OBJECT (totem->bvw),
4111 			"button-press-event",
4112 			G_CALLBACK (on_video_button_press_event),
4113 			totem);
4114 	g_signal_connect (G_OBJECT (totem->bvw),
4115 			"eos",
4116 			G_CALLBACK (on_eos_event),
4117 			totem);
4118 	g_signal_connect (G_OBJECT (totem->bvw),
4119 			"got-redirect",
4120 			G_CALLBACK (on_got_redirect),
4121 			totem);
4122 	g_signal_connect (G_OBJECT(totem->bvw),
4123 			"channels-change",
4124 			G_CALLBACK (on_channels_change_event),
4125 			totem);
4126 	g_signal_connect (G_OBJECT (totem->bvw),
4127 			"tick",
4128 			G_CALLBACK (update_current_time),
4129 			totem);
4130 	g_signal_connect (G_OBJECT (totem->bvw),
4131 			"got-metadata",
4132 			G_CALLBACK (on_got_metadata_event),
4133 			totem);
4134 	g_signal_connect (G_OBJECT (totem->bvw),
4135 			"buffering",
4136 			G_CALLBACK (on_buffering_event),
4137 			totem);
4138 	g_signal_connect (G_OBJECT (totem->bvw),
4139 			"download-buffering",
4140 			G_CALLBACK (on_download_buffering_event),
4141 			totem);
4142 	g_signal_connect (G_OBJECT (totem->bvw),
4143 			"error",
4144 			G_CALLBACK (on_error_event),
4145 			totem);
4146 	g_signal_connect (G_OBJECT (totem->bvw),
4147 			  "seek-requested",
4148 			  G_CALLBACK (on_seek_requested_event),
4149 			  totem);
4150 	g_signal_connect (G_OBJECT (totem->bvw),
4151 			  "track-skip-requested",
4152 			  G_CALLBACK (on_track_skip_requested_event),
4153 			  totem);
4154 	g_signal_connect (G_OBJECT (totem->bvw),
4155 			  "volume-change-requested",
4156 			  G_CALLBACK (on_volume_change_requested_event),
4157 			  totem);
4158 
4159 	container = GTK_CONTAINER (gtk_builder_get_object (totem->xml, "tmw_bvw_box"));
4160 	gtk_container_add (container,
4161 			GTK_WIDGET (totem->bvw));
4162 
4163 	add_fullscreen_toolbar (totem);
4164 
4165 	/* Events for the widget video window as well */
4166 	gtk_widget_add_events (GTK_WIDGET (totem->bvw),
4167 			GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
4168 	g_signal_connect (G_OBJECT(totem->bvw), "key_press_event",
4169 			G_CALLBACK (window_key_press_event_cb), totem);
4170 	g_signal_connect (G_OBJECT(totem->bvw), "key_release_event",
4171 			G_CALLBACK (window_key_press_event_cb), totem);
4172 
4173 	g_signal_connect (G_OBJECT (totem->bvw), "drag_data_received",
4174 			G_CALLBACK (drop_video_cb), totem);
4175 	gtk_drag_dest_set (GTK_WIDGET (totem->bvw), GTK_DEST_DEFAULT_ALL,
4176 			   target_table, G_N_ELEMENTS (target_table),
4177 			   GDK_ACTION_MOVE);
4178 
4179 	bvw = &(totem->bvw);
4180 	g_object_add_weak_pointer (G_OBJECT (totem->bvw),
4181 				   (gpointer *) bvw);
4182 
4183 	gtk_widget_show (GTK_WIDGET (totem->bvw));
4184 
4185 	/* FIXME: Otherwise we get a visible but
4186 	 * not realized widget ?!?! */
4187 	gtk_widget_realize (GTK_WIDGET (totem->bvw));
4188 }
4189 
4190 /**
4191  * totem_object_get_supported_content_types:
4192  *
4193  * Get the full list of file content types which Totem supports playing.
4194  *
4195  * Return value: (array zero-terminated=1) (transfer none): a %NULL-terminated array of the content types Totem supports
4196  * Since: 3.1.5
4197  */
4198 const gchar * const *
totem_object_get_supported_content_types(void)4199 totem_object_get_supported_content_types (void)
4200 {
4201 	return mime_types;
4202 }
4203 
4204 /**
4205  * totem_object_get_supported_uri_schemes:
4206  *
4207  * Get the full list of URI schemes which Totem supports accessing.
4208  *
4209  * Return value: (array zero-terminated=1) (transfer none): a %NULL-terminated array of the URI schemes Totem supports
4210  * Since: 3.1.5
4211  */
4212 const gchar * const *
totem_object_get_supported_uri_schemes(void)4213 totem_object_get_supported_uri_schemes (void)
4214 {
4215 	return uri_schemes;
4216 }
4217