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