/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2001-2007 Bastien Nocera * Copyright (C) 2007 Sunil Mohan Adapa * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * The Totem project hereby grant permission for non-gpl compatible GStreamer * plugins to be used and distributed together with GStreamer and Totem. This * permission are above and beyond the permissions granted by the GPL license * Totem is covered by. * */ #include #include #include #include #include #include #include "totem-fullscreen.h" #include "totem-interface.h" #include "totem-time-label.h" #include "bacon-video-widget.h" #include "gd-fullscreen-filter.h" #define FULLSCREEN_POPUP_TIMEOUT 5 #define FULLSCREEN_MOTION_TIME 200 /* in milliseconds */ #define FULLSCREEN_MOTION_NUM_EVENTS 15 static void totem_fullscreen_dispose (GObject *object); static void totem_fullscreen_finalize (GObject *object); static gboolean totem_fullscreen_popup_hide (TotemFullscreen *fs); /* Callback functions for GtkBuilder */ G_MODULE_EXPORT gboolean totem_fullscreen_vol_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, TotemFullscreen *fs); G_MODULE_EXPORT gboolean totem_fullscreen_vol_slider_released_cb (GtkWidget *widget, GdkEventButton *event, TotemFullscreen *fs); G_MODULE_EXPORT gboolean totem_fullscreen_seek_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, TotemFullscreen *fs); G_MODULE_EXPORT gboolean totem_fullscreen_seek_slider_released_cb (GtkWidget *widget, GdkEventButton *event, TotemFullscreen *fs); G_MODULE_EXPORT gboolean totem_fullscreen_control_enter_notify (GtkWidget *widget, GdkEventCrossing *event, TotemFullscreen *fs); G_MODULE_EXPORT gboolean totem_fullscreen_control_leave_notify (GtkWidget *widget, GdkEventCrossing *event, TotemFullscreen *fs); struct _TotemFullscreenPrivate { BaconVideoWidget *bvw; GtkWidget *parent_window; /* Fullscreen Popups */ GtkWidget *exit_popup; GtkWidget *control_popup; /* Locks for keeping the popups during adjustments */ gboolean seek_lock; guint popup_timeout; gboolean popup_in_progress; gboolean pointer_on_control; GdFullscreenFilter *filter; gint64 motion_start_time; guint motion_num_events; gboolean is_fullscreen; GtkBuilder *xml; }; #define TOTEM_FULLSCREEN_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_FULLSCREEN, TotemFullscreenPrivate)) G_DEFINE_TYPE (TotemFullscreen, totem_fullscreen, G_TYPE_OBJECT) gboolean totem_fullscreen_is_fullscreen (TotemFullscreen *fs) { g_return_val_if_fail (TOTEM_IS_FULLSCREEN (fs), FALSE); return (fs->priv->is_fullscreen != FALSE); } static void totem_fullscreen_move_popups (TotemFullscreen *fs) { int exit_width, exit_height; int control_width, control_height; GdkScreen *screen; GdkRectangle fullscreen_rect; GdkWindow *window; TotemFullscreenPrivate *priv = fs->priv; g_return_if_fail (priv->parent_window != NULL); /* Obtain the screen rectangle */ screen = gtk_window_get_screen (GTK_WINDOW (priv->parent_window)); window = gtk_widget_get_window (priv->parent_window); gdk_screen_get_monitor_geometry (screen, gdk_screen_get_monitor_at_window (screen, window), &fullscreen_rect); /* Get the popup window sizes */ gtk_window_get_size (GTK_WINDOW (priv->exit_popup), &exit_width, &exit_height); gtk_window_get_size (GTK_WINDOW (priv->control_popup), &control_width, &control_height); /* We take the full width of the screen */ gtk_window_resize (GTK_WINDOW (priv->control_popup), fullscreen_rect.width, control_height); if (gtk_widget_get_direction (priv->exit_popup) == GTK_TEXT_DIR_RTL) { gtk_window_move (GTK_WINDOW (priv->exit_popup), fullscreen_rect.x, fullscreen_rect.y); gtk_window_move (GTK_WINDOW (priv->control_popup), fullscreen_rect.width - control_width, fullscreen_rect.height + fullscreen_rect.y - control_height); } else { gtk_window_move (GTK_WINDOW (priv->exit_popup), fullscreen_rect.width + fullscreen_rect.x - exit_width, fullscreen_rect.y); gtk_window_move (GTK_WINDOW (priv->control_popup), fullscreen_rect.x, fullscreen_rect.height + fullscreen_rect.y - control_height); } } static void totem_fullscreen_size_changed_cb (GdkScreen *screen, TotemFullscreen *fs) { totem_fullscreen_move_popups (fs); } static void totem_fullscreen_theme_changed_cb (GtkIconTheme *icon_theme, TotemFullscreen *fs) { totem_fullscreen_move_popups (fs); } static void totem_fullscreen_window_realize_cb (GtkWidget *widget, TotemFullscreen *fs) { GdkScreen *screen; screen = gtk_widget_get_screen (widget); g_signal_connect (G_OBJECT (screen), "size-changed", G_CALLBACK (totem_fullscreen_size_changed_cb), fs); g_signal_connect (G_OBJECT (gtk_icon_theme_get_for_screen (screen)), "changed", G_CALLBACK (totem_fullscreen_theme_changed_cb), fs); } static void totem_fullscreen_window_unrealize_cb (GtkWidget *widget, TotemFullscreen *fs) { GdkScreen *screen; screen = gtk_widget_get_screen (widget); g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (totem_fullscreen_size_changed_cb), fs); g_signal_handlers_disconnect_by_func (gtk_icon_theme_get_for_screen (screen), G_CALLBACK (totem_fullscreen_theme_changed_cb), fs); } static gboolean totem_fullscreen_exit_popup_draw_cb (GtkWidget *widget, cairo_t *cr, TotemFullscreen *fs) { GdkScreen *screen; screen = gtk_widget_get_screen (widget); if (gdk_screen_is_composited (screen) == FALSE) return FALSE; gtk_widget_set_app_paintable (widget, TRUE); cairo_set_source_rgba (cr, 1., 1., 1., 0.); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); return FALSE; } gboolean totem_fullscreen_seek_slider_pressed_cb (GtkWidget *widget, GdkEventButton *event, TotemFullscreen *fs) { fs->priv->seek_lock = TRUE; return FALSE; } gboolean totem_fullscreen_seek_slider_released_cb (GtkWidget *widget, GdkEventButton *event, TotemFullscreen *fs) { fs->priv->seek_lock = FALSE; return FALSE; } static void totem_fullscreen_popup_timeout_add (TotemFullscreen *fs) { fs->priv->popup_timeout = g_timeout_add_seconds (FULLSCREEN_POPUP_TIMEOUT, (GSourceFunc) totem_fullscreen_popup_hide, fs); } static void totem_fullscreen_popup_timeout_remove (TotemFullscreen *fs) { if (fs->priv->popup_timeout != 0) { g_source_remove (fs->priv->popup_timeout); fs->priv->popup_timeout = 0; } } static void totem_fullscreen_set_cursor (TotemFullscreen *fs, gboolean state) { if (fs->priv->bvw != NULL) bacon_video_widget_set_show_cursor (fs->priv->bvw, state); } static gboolean totem_fullscreen_is_volume_popup_visible (TotemFullscreen *fs) { return gtk_widget_get_visible (gtk_scale_button_get_popup (GTK_SCALE_BUTTON (fs->volume))); } static void totem_fullscreen_force_popup_hide (TotemFullscreen *fs) { /* Popdown the volume button if it's visible */ if (totem_fullscreen_is_volume_popup_visible (fs)) gtk_bindings_activate (G_OBJECT (fs->volume), GDK_KEY_Escape, 0); gtk_widget_hide (fs->priv->exit_popup); gtk_widget_hide (fs->priv->control_popup); totem_fullscreen_popup_timeout_remove (fs); totem_fullscreen_set_cursor (fs, FALSE); } static gboolean totem_fullscreen_popup_hide (TotemFullscreen *fs) { if (fs->priv->bvw == NULL || totem_fullscreen_is_fullscreen (fs) == FALSE) return TRUE; if (fs->priv->seek_lock != FALSE || totem_fullscreen_is_volume_popup_visible (fs) != FALSE) return TRUE; totem_fullscreen_force_popup_hide (fs); return FALSE; } static void totem_fullscreen_motion_notify (GdFullscreenFilter *filter, TotemFullscreen *fs) { gint64 motion_delay; gint64 curr; curr = g_get_monotonic_time (); /* Only after FULLSCREEN_MOTION_NUM_EVENTS motion events, in FULLSCREEN_MOTION_TIME milliseconds will we show the popups */ motion_delay = (curr - fs->priv->motion_start_time) / 1000; if (fs->priv->motion_start_time == 0 || motion_delay < 0 || motion_delay > FULLSCREEN_MOTION_TIME) { fs->priv->motion_start_time = curr; fs->priv->motion_num_events = 0; return; } fs->priv->motion_num_events++; if (!fs->priv->pointer_on_control && fs->priv->motion_num_events > FULLSCREEN_MOTION_NUM_EVENTS) { totem_fullscreen_show_popups (fs, TRUE); } } void totem_fullscreen_show_popups (TotemFullscreen *fs, gboolean show_cursor) { GtkWidget *item; g_assert (fs->priv->is_fullscreen != FALSE); if (fs->priv->popup_in_progress != FALSE) return; if (gtk_window_is_active (GTK_WINDOW (fs->priv->parent_window)) == FALSE) return; fs->priv->popup_in_progress = TRUE; totem_fullscreen_popup_timeout_remove (fs); /* FIXME: is this really required while we are anyway going to do a show_all on its parent control_popup? */ item = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tcw_hbox")); gtk_widget_show_all (item); gdk_flush (); /* Show the popup widgets */ totem_fullscreen_move_popups (fs); gtk_widget_show_all (fs->priv->exit_popup); gtk_widget_show_all (fs->priv->control_popup); if (show_cursor != FALSE) { /* Show the mouse cursor */ totem_fullscreen_set_cursor (fs, TRUE); } /* Reset the popup timeout */ totem_fullscreen_popup_timeout_add (fs); fs->priv->popup_in_progress = FALSE; } void totem_fullscreen_show_popups_or_osd (TotemFullscreen *fs, const char *icon_name, gboolean show_cursor) { if (icon_name == NULL) { totem_fullscreen_show_popups (fs, show_cursor); return; } bacon_video_widget_popup_osd (fs->priv->bvw, icon_name); } G_MODULE_EXPORT gboolean totem_fullscreen_control_enter_notify (GtkWidget *widget, GdkEventCrossing *event, TotemFullscreen *fs) { fs->priv->pointer_on_control = TRUE; totem_fullscreen_popup_timeout_remove (fs); return TRUE; } G_MODULE_EXPORT gboolean totem_fullscreen_control_leave_notify (GtkWidget *widget, GdkEventCrossing *event, TotemFullscreen *fs) { fs->priv->pointer_on_control = FALSE; return TRUE; } void totem_fullscreen_set_fullscreen (TotemFullscreen *fs, gboolean fullscreen) { g_return_if_fail (TOTEM_IS_FULLSCREEN (fs)); totem_fullscreen_force_popup_hide (fs); bacon_video_widget_set_fullscreen (fs->priv->bvw, fullscreen); totem_fullscreen_set_cursor (fs, !fullscreen); fs->priv->is_fullscreen = fullscreen; if (fullscreen == FALSE) gd_fullscreen_filter_stop (fs->priv->filter); else gd_fullscreen_filter_start (fs->priv->filter); } static void totem_fullscreen_parent_window_notify (GtkWidget *parent_window, GParamSpec *property, TotemFullscreen *fs) { GtkWidget *popup; if (totem_fullscreen_is_fullscreen (fs) == FALSE) return; popup = gtk_scale_button_get_popup (GTK_SCALE_BUTTON (fs->volume)); if (parent_window == fs->priv->parent_window && gtk_window_is_active (GTK_WINDOW (parent_window)) == FALSE && gtk_widget_get_visible (popup) == FALSE) { totem_fullscreen_force_popup_hide (fs); totem_fullscreen_set_cursor (fs, TRUE); } else { totem_fullscreen_set_cursor (fs, FALSE); } } TotemFullscreen * totem_fullscreen_new (GtkWindow *toplevel_window) { TotemFullscreen *fs = TOTEM_FULLSCREEN (g_object_new (TOTEM_TYPE_FULLSCREEN, NULL)); if (fs->priv->xml == NULL) { g_object_unref (fs); return NULL; } totem_fullscreen_set_parent_window (fs, toplevel_window); fs->time_label = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tcw_time_display_label")); fs->buttons_box = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tcw_buttons_hbox")); fs->exit_button = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tefw_fs_exit_button")); /* Volume */ fs->volume = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tcw_volume_button")); /* Seek */ fs->seek = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tcw_seek_hscale")); /* Motion notify */ fs->priv->filter = gd_fullscreen_filter_new (); g_signal_connect (G_OBJECT (fs->priv->filter), "motion-event", G_CALLBACK (totem_fullscreen_motion_notify), fs); gtk_widget_add_events (fs->seek, GDK_POINTER_MOTION_MASK); gtk_widget_add_events (fs->exit_button, GDK_POINTER_MOTION_MASK); return fs; } void totem_fullscreen_set_video_widget (TotemFullscreen *fs, BaconVideoWidget *bvw) { g_return_if_fail (TOTEM_IS_FULLSCREEN (fs)); g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw)); g_return_if_fail (fs->priv->bvw == NULL); fs->priv->bvw = bvw; } void totem_fullscreen_set_parent_window (TotemFullscreen *fs, GtkWindow *parent_window) { g_return_if_fail (TOTEM_IS_FULLSCREEN (fs)); g_return_if_fail (GTK_IS_WINDOW (parent_window)); g_return_if_fail (fs->priv->parent_window == NULL); fs->priv->parent_window = GTK_WIDGET (parent_window); /* Screen size and Theme changes */ g_signal_connect (fs->priv->parent_window, "realize", G_CALLBACK (totem_fullscreen_window_realize_cb), fs); g_signal_connect (fs->priv->parent_window, "unrealize", G_CALLBACK (totem_fullscreen_window_unrealize_cb), fs); g_signal_connect (G_OBJECT (fs->priv->parent_window), "notify::is-active", G_CALLBACK (totem_fullscreen_parent_window_notify), fs); } static void totem_fullscreen_init (TotemFullscreen *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TOTEM_TYPE_FULLSCREEN, TotemFullscreenPrivate); self->priv->seek_lock = FALSE; self->priv->xml = totem_interface_load ("fullscreen.ui", TRUE, NULL, self); if (self->priv->xml == NULL) return; self->priv->pointer_on_control = FALSE; self->priv->exit_popup = GTK_WIDGET (gtk_builder_get_object (self->priv->xml, "totem_exit_fullscreen_window")); g_signal_connect (G_OBJECT (self->priv->exit_popup), "draw", G_CALLBACK (totem_fullscreen_exit_popup_draw_cb), self); self->priv->control_popup = GTK_WIDGET (gtk_builder_get_object (self->priv->xml, "totem_controls_window")); /* Motion notify */ gtk_widget_add_events (self->priv->exit_popup, GDK_POINTER_MOTION_MASK); gtk_widget_add_events (self->priv->control_popup, GDK_POINTER_MOTION_MASK); } static void totem_fullscreen_dispose (GObject *object) { TotemFullscreenPrivate *priv = TOTEM_FULLSCREEN_GET_PRIVATE (object); if (priv->xml != NULL) { g_clear_object (&priv->xml); gtk_widget_destroy (priv->exit_popup); gtk_widget_destroy (priv->control_popup); } G_OBJECT_CLASS (totem_fullscreen_parent_class)->dispose (object); } static void totem_fullscreen_finalize (GObject *object) { TotemFullscreen *fs = TOTEM_FULLSCREEN (object); totem_fullscreen_popup_timeout_remove (fs); g_clear_object (&fs->priv->filter); g_signal_handlers_disconnect_by_func (fs->priv->parent_window, G_CALLBACK (totem_fullscreen_window_realize_cb), fs); g_signal_handlers_disconnect_by_func (fs->priv->parent_window, G_CALLBACK (totem_fullscreen_window_unrealize_cb), fs); G_OBJECT_CLASS (totem_fullscreen_parent_class)->finalize (object); } static void totem_fullscreen_class_init (TotemFullscreenClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (TotemFullscreenPrivate)); object_class->dispose = totem_fullscreen_dispose; object_class->finalize = totem_fullscreen_finalize; } void totem_fullscreen_set_title (TotemFullscreen *fs, const char *title) { GtkLabel *widget; char *text; g_return_if_fail (TOTEM_IS_FULLSCREEN (fs)); widget = GTK_LABEL (gtk_builder_get_object (fs->priv->xml, "tcw_title_label")); if (title != NULL) { char *escaped; escaped = g_markup_escape_text (title, -1); text = g_strdup_printf ("%s", escaped); g_free (escaped); } else { text = g_strdup_printf ("%s", _("No File")); } gtk_label_set_markup (widget, text); g_free (text); } void totem_fullscreen_set_seekable (TotemFullscreen *fs, gboolean seekable) { GtkWidget *item; g_return_if_fail (TOTEM_IS_FULLSCREEN (fs)); item = GTK_WIDGET (gtk_builder_get_object (fs->priv->xml, "tcw_time_hbox")); gtk_widget_set_sensitive (item, seekable); gtk_widget_set_sensitive (fs->seek, seekable); } void totem_fullscreen_set_can_set_volume (TotemFullscreen *fs, gboolean can_set_volume) { g_return_if_fail (TOTEM_IS_FULLSCREEN (fs)); gtk_widget_set_sensitive (fs->volume, can_set_volume); }