No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
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 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 */
27
28 #include "config.h"
29
30 #include <string.h>
31 #include <glib.h>
32 #include <glib/gi18n.h>
33
34 #include "rb-visualizer-page.h"
35 #include "rb-visualizer-fullscreen.h"
36
37 #include <widgets/rb-dialog.h>
38 #include <lib/rb-util.h>
39 #include <lib/rb-debug.h>
40
41
42 G_DEFINE_DYNAMIC_TYPE (RBVisualizerPage, rb_visualizer_page, RB_TYPE_DISPLAY_PAGE)
43
44 static GtkWidget *create_embed (RBVisualizerPage *page);
45
46 enum {
47 PROP_0,
48 PROP_SINK,
49 PROP_FULLSCREEN_ACTION,
50 PROP_POPUP
51 };
52
53 enum {
54 START,
55 STOP,
56 FULLSCREEN,
57 LAST_SIGNAL
58 };
59
60 static guint signals[LAST_SIGNAL] = {0,};
61
62 RBVisualizerPage *
63 rb_visualizer_page_new (GObject *plugin, RBShell *shell, GtkToggleAction *fullscreen, GtkWidget *popup)
64 {
65 GObject *page;
66 GdkPixbuf *pixbuf;
67 gint size;
68
69 gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
70 pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
71 "visualization",
72 size,
73 0, NULL);
74
75 page = g_object_new (RB_TYPE_VISUALIZER_PAGE,
76 "plugin", plugin,
77 "shell", shell,
78 "name", _("Visual Effects"),
79 "pixbuf", pixbuf,
80 "fullscreen-action", fullscreen,
81 "popup", popup,
82 NULL);
83 if (pixbuf != NULL) {
84 g_object_unref (pixbuf);
85 }
86
87 return RB_VISUALIZER_PAGE (page);
88 }
89
90 static void
91 set_action_state (RBVisualizerPage *page, gboolean active)
92 {
93 page->setting_state = TRUE;
94 g_object_set (page->fullscreen_action, "active", active, NULL);
95 page->setting_state = FALSE;
96 }
97
98 static void
99 start_fullscreen (RBVisualizerPage *page)
100 {
101 if (page->fullscreen == NULL) {
102 ClutterActor *stage;
103 GtkWindow *main_window;
104 RBShell *shell;
105 int x, y;
106
107 rb_debug ("starting fullscreen display");
108 g_object_get (page, "shell", &shell, NULL);
109 g_object_get (shell, "window", &main_window, NULL);
110
111 page->fullscreen = gtk_window_new (GTK_WINDOW_TOPLEVEL);
112 gtk_window_set_skip_taskbar_hint (GTK_WINDOW (page->fullscreen), TRUE);
113
114 /* move the texture from the page embed to the new fullscreen embed */
115
116 g_object_ref (page->texture);
117
118 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (page->embed));
119 clutter_container_remove_actor (CLUTTER_CONTAINER (stage), page->texture);
120
121 page->fullscreen_embed = create_embed (page);
122 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (page->fullscreen_embed));
123 clutter_container_add (CLUTTER_CONTAINER (stage), page->texture, NULL);
124 g_object_unref (page->texture);
125
126 gtk_container_add (GTK_CONTAINER (page->fullscreen), page->fullscreen_embed);
127 gtk_widget_show_all (GTK_WIDGET (page->fullscreen));
128
129 gtk_window_get_position (main_window, &x, &y);
130 gtk_window_move (GTK_WINDOW (page->fullscreen), x, y);
131
132 gtk_window_fullscreen (GTK_WINDOW (page->fullscreen));
133 gtk_window_set_transient_for (GTK_WINDOW (page->fullscreen), main_window);
134 g_object_unref (main_window);
135
136 rb_visualizer_fullscreen_add_widgets (page->fullscreen, stage, shell);
137 g_object_unref (shell);
138 }
139
140 set_action_state (page, TRUE);
141 }
142
143 static void
144 stop_fullscreen (RBVisualizerPage *page)
145 {
146 if (page->fullscreen != NULL) {
147 ClutterActor *stage;
148
149 rb_debug ("stopping fullscreen display");
150
151 g_object_ref (page->texture);
152 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (page->fullscreen_embed));
153 rb_visualizer_fullscreen_stop (stage);
154 clutter_container_remove_actor (CLUTTER_CONTAINER (stage), page->texture);
155
156 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (page->embed));
157 clutter_container_add (CLUTTER_CONTAINER (stage), page->texture, NULL);
158
159 g_object_unref (page->texture);
160
161 gtk_widget_destroy (GTK_WIDGET (page->fullscreen));
162 page->fullscreen = NULL;
163
164 page->fullscreen_embed = NULL;
165 }
166
167 set_action_state (page, FALSE);
168 }
169
170 static void
171 toggle_fullscreen (RBVisualizerPage *page)
172 {
173 if (page->fullscreen != NULL) {
174 stop_fullscreen (page);
175 } else {
176 start_fullscreen (page);
177 }
178 }
179
180 static void
181 toggle_fullscreen_cb (GtkAction *action, RBVisualizerPage *page)
182 {
183 if (page->setting_state == FALSE) {
184 toggle_fullscreen (page);
185 }
186 }
187
188 static gboolean
189 stage_button_press_cb (ClutterActor *stage, ClutterEvent *event, RBVisualizerPage *page)
190 {
191 if (event->button.button == 1 && event->button.click_count == 2) {
192 clutter_threads_leave ();
193 toggle_fullscreen (page);
194 clutter_threads_enter ();
195 } else if (event->button.button == 3) {
196 rb_display_page_show_popup (RB_DISPLAY_PAGE (page));
197 }
198
199 return FALSE;
200 }
201
202 static gboolean
203 stage_key_release_cb (ClutterActor *stage, ClutterEvent *event, RBVisualizerPage *page)
204 {
205 if (event->key.keyval == CLUTTER_KEY_Escape) {
206 clutter_threads_leave ();
207 stop_fullscreen (page);
208 clutter_threads_enter ();
209 }
210 return FALSE;
211 }
212
213 static void
214 resize_sink_texture (ClutterActor *stage, ClutterActorBox *box, ClutterAllocationFlags flags, ClutterActor *texture)
215 {
216 clutter_actor_set_size (texture, box->x2 - box->x1, box->y2 - box->y1);
217 }
218
219
220 static GtkWidget *
221 create_embed (RBVisualizerPage *page)
222 {
223 ClutterActor *stage;
224 GtkWidget *embed;
225
226 embed = gtk_clutter_embed_new ();
227
228 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (embed));
229 g_signal_connect_object (stage, "allocation-changed", G_CALLBACK (resize_sink_texture), page->texture, 0);
230 g_signal_connect_object (stage, "button-press-event", G_CALLBACK (stage_button_press_cb), page, 0);
231 g_signal_connect_object (stage, "key-release-event", G_CALLBACK (stage_key_release_cb), page, 0);
232
233 return embed;
234 }
235
236 static gboolean
237 impl_show_popup (RBDisplayPage *page)
238 {
239 RBVisualizerPage *vpage = RB_VISUALIZER_PAGE (page);
240 gtk_menu_popup (GTK_MENU (vpage->popup), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time ());
241 return TRUE;
242 }
243
244 static void
245 impl_selected (RBDisplayPage *bpage)
246 {
247 RBVisualizerPage *page = RB_VISUALIZER_PAGE (bpage);
248
249 RB_DISPLAY_PAGE_CLASS (rb_visualizer_page_parent_class)->selected (bpage);
250
251 if (page->embed == NULL) {
252 ClutterActor *stage;
253
254 page->embed = create_embed (page);
255
256 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (page->embed));
257
258 clutter_container_add (CLUTTER_CONTAINER (stage), page->texture, NULL);
259
260 gtk_box_pack_start (GTK_BOX (page), page->embed, TRUE, TRUE, 0);
261 gtk_widget_show_all (GTK_WIDGET (page));
262 }
263
264 g_signal_emit (page, signals[START], 0);
265 }
266
267 static void
268 impl_deselected (RBDisplayPage *bpage)
269 {
270 RBVisualizerPage *page = RB_VISUALIZER_PAGE (bpage);
271
272 RB_DISPLAY_PAGE_CLASS (rb_visualizer_page_parent_class)->deselected (bpage);
273
274 if (page->fullscreen == NULL) {
275 g_signal_emit (page, signals[STOP], 0);
276 } else {
277 /* might as well leave it running.. */
278 }
279 }
280
281 static void
282 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
283 {
284 RBVisualizerPage *page = RB_VISUALIZER_PAGE (object);
285
286 switch (prop_id) {
287 case PROP_SINK:
288 g_value_set_object (value, page->sink);
289 break;
290 case PROP_POPUP:
291 g_value_set_object (value, page->popup);
292 break;
293 case PROP_FULLSCREEN_ACTION:
294 g_value_set_object (value, page->fullscreen_action);
295 break;
296 default:
297 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
298 break;
299 }
300 }
301
302 static void
303 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
304 {
305 RBVisualizerPage *page = RB_VISUALIZER_PAGE (object);
306
307 switch (prop_id) {
308 case PROP_POPUP:
309 page->popup = g_value_get_object (value);
310 break;
311 case PROP_FULLSCREEN_ACTION:
312 page->fullscreen_action = g_value_get_object (value);
313 break;
314 default:
315 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
316 break;
317 }
318 }
319
320 static void
321 impl_dispose (GObject *object)
322 {
323 RBVisualizerPage *page = RB_VISUALIZER_PAGE (object);
324
325 if (page->embed != NULL) {
326 gtk_container_remove (GTK_CONTAINER (page), page->embed);
327 page->embed = NULL;
328 }
329 if (page->sink != NULL) {
330 g_object_unref (page->sink);
331 page->sink = NULL;
332 }
333 if (page->popup != NULL) {
334 g_object_unref (page->popup);
335 page->popup = NULL;
336 }
337
338 G_OBJECT_CLASS (rb_visualizer_page_parent_class)->dispose (object);
339 }
340
341 static void
342 impl_constructed (GObject *object)
343 {
344 RBVisualizerPage *page;
345 ClutterInitError err;
346 GstElement *colorspace;
347 GstElement *realsink;
348 GstElement *capsfilter;
349 GstCaps *caps;
350 GstPad *pad;
351
352 RB_CHAIN_GOBJECT_METHOD (rb_visualizer_page_parent_class, constructed, object);
353 page = RB_VISUALIZER_PAGE (object);
354
355 err = gtk_clutter_init (NULL, NULL);
356 if (err != CLUTTER_INIT_SUCCESS) {
357 /* maybe do something more sensible here. not sure if there are any user-recoverable
358 * conditions that would cause clutter init to fail, though, so it may not be worth it.
359 * as it is, we just won't add the page to the page tree.
360 */
361 g_warning ("Unable to display visual effects due to Clutter init failure");
362 return;
363 }
364
365 page->texture = clutter_texture_new ();
366 clutter_texture_set_sync_size (CLUTTER_TEXTURE (page->texture), TRUE);
367 clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (page->texture), TRUE);
368
369 page->sink = gst_bin_new (NULL);
370 g_object_ref (page->sink);
371
372 /* actual sink */
373 realsink = gst_element_factory_make ("cluttersink", NULL);
374 g_object_set (realsink, "texture", page->texture, NULL);
375
376 colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
377 /* capsfilter to force rgb format (without this we end up using ayuv) */
378 capsfilter = gst_element_factory_make ("capsfilter", NULL);
379 caps = gst_caps_from_string ("video/x-raw-rgb,bpp=(int)24,depth=(int)24,"
380 "endianness=(int)4321,red_mask=(int)16711680,"
381 "green_mask=(int)65280,blue_mask=(int)255");
382 g_object_set (capsfilter, "caps", caps, NULL);
383 gst_caps_unref (caps);
384
385 gst_bin_add_many (GST_BIN (page->sink), colorspace, capsfilter, realsink, NULL);
386 gst_element_link (colorspace, capsfilter);
387 gst_element_link (capsfilter, realsink);
388
389 pad = gst_element_get_static_pad (colorspace, "sink");
390 gst_element_add_pad (page->sink, gst_ghost_pad_new ("sink", pad));
391 gst_object_unref (pad);
392
393 g_signal_connect_object (page->fullscreen_action,
394 "toggled",
395 G_CALLBACK (toggle_fullscreen_cb),
396 page, 0);
397 }
398
399 static void
400 rb_visualizer_page_init (RBVisualizerPage *page)
401 {
402 }
403
404 static void
405 rb_visualizer_page_class_init (RBVisualizerPageClass *klass)
406 {
407 GObjectClass *object_class = G_OBJECT_CLASS (klass);
408 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
409
410 object_class->constructed = impl_constructed;
411 object_class->get_property = impl_get_property;
412 object_class->set_property = impl_set_property;
413 object_class->dispose = impl_dispose;
414
415 page_class->selected = impl_selected;
416 page_class->deselected = impl_deselected;
417 page_class->show_popup = impl_show_popup;
418
419 g_object_class_install_property (object_class,
420 PROP_SINK,
421 g_param_spec_object ("sink",
422 "sink",
423 "gstreamer sink element",
424 GST_TYPE_ELEMENT,
425 G_PARAM_READABLE));
426 g_object_class_install_property (object_class,
427 PROP_POPUP,
428 g_param_spec_object ("popup",
429 "popup",
430 "popup menu",
431 GTK_TYPE_WIDGET,
432 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
433 g_object_class_install_property (object_class,
434 PROP_FULLSCREEN_ACTION,
435 g_param_spec_object ("fullscreen-action",
436 "fullscreen action",
437 "GtkToggleAction for fullscreen",
438 GTK_TYPE_TOGGLE_ACTION,
439 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
440
441 signals[START] = g_signal_new ("start",
442 RB_TYPE_VISUALIZER_PAGE,
443 G_SIGNAL_RUN_LAST,
444 0,
445 NULL, NULL,
446 g_cclosure_marshal_VOID__VOID,
447 G_TYPE_NONE,
448 0);
449 signals[STOP] = g_signal_new ("stop",
450 RB_TYPE_VISUALIZER_PAGE,
451 G_SIGNAL_RUN_LAST,
452 0,
453 NULL, NULL,
454 g_cclosure_marshal_VOID__VOID,
455 G_TYPE_NONE,
456 0);
457 signals[FULLSCREEN] = g_signal_new_class_handler ("toggle-fullscreen",
458 RB_TYPE_VISUALIZER_PAGE,
459 G_SIGNAL_RUN_LAST,
460 G_CALLBACK (toggle_fullscreen),
461 NULL, NULL,
462 g_cclosure_marshal_VOID__VOID,
463 G_TYPE_NONE,
464 0);
465 }
466
467 static void
468 rb_visualizer_page_class_finalize (RBVisualizerPageClass *klass)
469 {
470 }
471
472 void
473 _rb_visualizer_page_register_type (GTypeModule *module)
474 {
475 rb_visualizer_page_register_type (module);
476 }