1 /*
2  * Copyright (c) 2017-2022 gnome-mpv
3  *
4  * This file is part of Celluloid.
5  *
6  * Celluloid is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Celluloid is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Celluloid.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <glib/gi18n.h>
21 #include <glib.h>
22 #include <glib/gprintf.h>
23 #include <gtk/gtk.h>
24 #include <glib-unix.h>
25 #include <locale.h>
26 
27 #include "celluloid-controller-private.h"
28 #include "celluloid-controller.h"
29 #include "celluloid-controller-actions.h"
30 #include "celluloid-controller-input.h"
31 #include "celluloid-player-options.h"
32 #include "celluloid-def.h"
33 
34 static void
35 constructed(GObject *object);
36 
37 static void
38 set_property(	GObject *object,
39 		guint property_id,
40 		const GValue *value,
41 		GParamSpec *pspec );
42 
43 static void
44 get_property(	GObject *object,
45 		guint property_id,
46 		GValue *value,
47 		GParamSpec *pspec );
48 
49 static void
50 dispose(GObject *object);
51 
52 static gboolean
53 loop_to_boolean(	GBinding *binding,
54 			const GValue *from_value,
55 			GValue *to_value,
56 			gpointer data );
57 
58 static gboolean
59 boolean_to_loop(	GBinding *binding,
60 			const GValue *from_value,
61 			GValue *to_value,
62 			gpointer data );
63 
64 static void
65 mpris_enable_handler(GSettings *settings, gchar *key, gpointer data);
66 
67 static void
68 media_keys_enable_handler(GSettings *settings, gchar *key, gpointer data);
69 
70 static void
71 view_ready_handler(CelluloidView *view, gpointer data);
72 
73 static void
74 render_handler(CelluloidView *view, gpointer data);
75 
76 static void
77 preferences_updated_handler(CelluloidView *view, gpointer data);
78 
79 static void
80 audio_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data);
81 
82 static void
83 video_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data);
84 
85 static void
86 subtitle_track_load_handler(	CelluloidView *view,
87 				const gchar *uri,
88 				gpointer data );
89 
90 static void
91 file_open_handler(	CelluloidView *view,
92 			GListModel *files,
93 			gboolean append,
94 			gpointer data );
95 
96 static void
97 is_active_handler(GObject *gobject, GParamSpec *pspec, gpointer data);
98 
99 static gboolean
100 close_request_handler(CelluloidView *view, gpointer data);
101 
102 static void
103 playlist_item_activated_handler(CelluloidView *view, gint pos, gpointer data);
104 
105 static void
106 playlist_item_inserted_handler(CelluloidView *view, gint pos, gpointer data);
107 
108 static void
109 playlist_item_deleted_handler(CelluloidView *view, gint pos, gpointer data);
110 
111 static void
112 playlist_reordered_handler(	CelluloidView *view,
113 				gint src,
114 				gint dst,
115 				gpointer data );
116 
117 static void
118 set_use_skip_button_for_playlist(	CelluloidController *controller,
119 					gboolean value);
120 
121 static void
122 connect_signals(CelluloidController *controller);
123 
124 static gboolean
125 update_seek_bar(gpointer data);
126 
127 static gboolean
128 is_more_than_one(	GBinding *binding,
129 			const GValue *from_value,
130 			GValue *to_value,
131 			gpointer data );
132 
133 static void
134 idle_active_handler(GObject *object, GParamSpec *pspec, gpointer data);
135 
136 static void
137 playlist_handler(GObject *object, GParamSpec *pspec, gpointer data);
138 
139 static void
140 vid_handler(GObject *object, GParamSpec *pspec, gpointer data);
141 
142 static void
143 window_scale_handler(GObject *object, GParamSpec *pspec, gpointer data);
144 
145 static void
146 model_ready_handler(GObject *object, GParamSpec *pspec, gpointer data);
147 
148 static void
149 playlist_replaced_handler(CelluloidModel *model, gpointer data);
150 
151 static void
152 frame_ready_handler(CelluloidModel *model, gpointer data);
153 
154 static void
155 metadata_update_handler(CelluloidModel *model, gint64 pos, gpointer data);
156 
157 static void
158 window_resize_handler(	CelluloidModel *model,
159 			gint64 width,
160 			gint64 height,
161 			gpointer data );
162 
163 static void
164 window_move_handler(	CelluloidModel *model,
165 			gboolean flip_x,
166 			gboolean flip_y,
167 			GValue *x,
168 			GValue *y,
169 			gpointer data );
170 
171 static void
172 message_handler(CelluloidMpv *mpv, const gchar *message, gpointer data);
173 
174 static void
175 error_handler(CelluloidMpv *mpv, const gchar *message, gpointer data);
176 
177 static void
178 shutdown_handler(CelluloidMpv *mpv, gpointer data);
179 
180 static gboolean
181 update_window_scale(gpointer data);
182 
183 static void
184 video_area_resize_handler(	CelluloidView *view,
185 				gint width,
186 				gint height,
187 				gpointer data );
188 
189 static void
190 searching_handler(GObject *object, GParamSpec *pspec, gpointer data);
191 
192 static void
193 fullscreen_handler(GObject *object, GParamSpec *pspec, gpointer data);
194 
195 static void
196 play_button_handler(GtkButton *button, gpointer data);
197 
198 static void
199 stop_button_handler(GtkButton *button, gpointer data);
200 
201 static void
202 forward_button_handler(GtkButton *button, gpointer data);
203 
204 static void
205 rewind_button_handler(GtkButton *button, gpointer data);
206 
207 static void
208 next_button_handler(GtkButton *button, gpointer data);
209 
210 static void
211 previous_button_handler(GtkButton *button, gpointer data);
212 
213 static void
214 fullscreen_button_handler(GtkButton *button, gpointer data);
215 
216 static void
217 seek_handler(GtkButton *button, gdouble value, gpointer data);
218 
219 static void
220 celluloid_controller_class_init(CelluloidControllerClass *klass);
221 
222 static void
223 celluloid_controller_init(CelluloidController *controller);
224 
225 static void
226 update_extra_mpv_options(CelluloidController *controller);
227 
G_DEFINE_TYPE(CelluloidController,celluloid_controller,G_TYPE_OBJECT)228 G_DEFINE_TYPE(CelluloidController, celluloid_controller, G_TYPE_OBJECT)
229 
230 static void
231 constructed(GObject *object)
232 {
233 	CelluloidController *controller;
234 	CelluloidMainWindow *window;
235 	CelluloidVideoArea *video_area;
236 	gboolean always_floating;
237 	gint64 wid;
238 
239 	controller = CELLULOID_CONTROLLER(object);
240 	always_floating =	g_settings_get_boolean
241 				(	controller->settings,
242 					"always-use-floating-controls" );
243 
244 	controller->view = celluloid_view_new(controller->app, always_floating);
245 	window = CELLULOID_MAIN_WINDOW(controller->view);
246 	video_area = celluloid_main_window_get_video_area(window);
247 	wid = celluloid_video_area_get_xid(video_area);
248 	controller->model = celluloid_model_new(wid);
249 
250 	connect_signals(controller);
251 	celluloid_controller_action_register_actions(controller);
252 	celluloid_controller_input_connect_signals(controller);
253 	update_extra_mpv_options(controller);
254 
255 	// If the window is already realized at this point (happens on X11), the
256 	// ready signal will never fire. Because of this, we need to manually
257 	// call view_ready_handler() to ensure that the model gets initialized.
258 	if(gtk_widget_get_realized(GTK_WIDGET(window)))
259 	{
260 		view_ready_handler(controller->view, controller);
261 	}
262 
263 	gtk_widget_add_controller(GTK_WIDGET(window), controller->key_controller);
264 	gtk_widget_show(GTK_WIDGET(window));
265 
266 	g_signal_connect(	controller->settings,
267 				"changed::mpris-enable",
268 				G_CALLBACK(mpris_enable_handler),
269 				controller );
270 	g_signal_connect(	controller->settings,
271 				"changed::media-keys-enable",
272 				G_CALLBACK(media_keys_enable_handler),
273 				controller );
274 
275 	if(g_settings_get_boolean(controller->settings, "mpris-enable"))
276 	{
277 		controller->mpris = celluloid_mpris_new(controller);
278 	}
279 
280 	if(g_settings_get_boolean(controller->settings, "media-keys-enable"))
281 	{
282 		controller->media_keys = celluloid_media_keys_new(controller);
283 	}
284 
285 	G_OBJECT_CLASS(celluloid_controller_parent_class)->constructed(object);
286 }
287 
288 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)289 set_property(	GObject *object,
290 		guint property_id,
291 		const GValue *value,
292 		GParamSpec *pspec )
293 {
294 	CelluloidController *self = CELLULOID_CONTROLLER(object);
295 
296 	switch(property_id)
297 	{
298 		case PROP_APP:
299 		self->app = g_value_get_pointer(value);
300 		break;
301 
302 		case PROP_READY:
303 		self->ready = g_value_get_boolean(value);
304 		break;
305 
306 		case PROP_IDLE:
307 		self->idle = g_value_get_boolean(value);
308 		break;
309 
310 		case PROP_USE_SKIP_BUTTONS_FOR_PLAYLIST:
311 		self->use_skip_buttons_for_playlist = g_value_get_boolean(value);
312 		set_use_skip_button_for_playlist
313 			(self, self->use_skip_buttons_for_playlist);
314 		break;
315 
316 		default:
317 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
318 		break;
319 	}
320 }
321 
322 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)323 get_property(	GObject *object,
324 		guint property_id,
325 		GValue *value,
326 		GParamSpec *pspec )
327 {
328 	CelluloidController *self = CELLULOID_CONTROLLER(object);
329 
330 	switch(property_id)
331 	{
332 		case PROP_APP:
333 		g_value_set_pointer(value, self->app);
334 		break;
335 
336 		case PROP_READY:
337 		g_value_set_boolean(value, self->ready);
338 		break;
339 
340 		case PROP_IDLE:
341 		g_value_set_boolean(value, self->idle);
342 		break;
343 
344 		case PROP_USE_SKIP_BUTTONS_FOR_PLAYLIST:
345 		g_value_set_boolean(value, self->use_skip_buttons_for_playlist);
346 		break;
347 
348 		default:
349 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
350 		break;
351 	}
352 }
353 
354 static void
dispose(GObject * object)355 dispose(GObject *object)
356 {
357 	CelluloidController *controller = CELLULOID_CONTROLLER(object);
358 
359 	g_clear_object(&controller->settings);
360 	g_clear_object(&controller->mpris);
361 	g_clear_object(&controller->media_keys);
362 
363 	g_source_clear(&controller->update_seekbar_id);
364 	g_source_clear(&controller->resize_timeout_tag);
365 
366 	if(controller->view)
367 	{
368 		celluloid_view_make_gl_context_current(controller->view);
369 		g_clear_object(&controller->model);
370 		g_object_unref(controller->view);
371 		controller->view = NULL;
372 	}
373 
374 	G_OBJECT_CLASS(celluloid_controller_parent_class)->dispose(object);
375 }
376 
377 static gboolean
loop_to_boolean(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer data)378 loop_to_boolean(	GBinding *binding,
379 			const GValue *from_value,
380 			GValue *to_value,
381 			gpointer data )
382 {
383 	const gchar *from = g_value_get_string(from_value);
384 	gboolean to = g_strcmp0(from, "no") != 0;
385 
386 	g_value_set_boolean(to_value, to);
387 
388 	return TRUE;
389 }
390 
391 static gboolean
boolean_to_loop(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer data)392 boolean_to_loop(	GBinding *binding,
393 			const GValue *from_value,
394 			GValue *to_value,
395 			gpointer data )
396 {
397 	gboolean from = g_value_get_boolean(from_value);
398 	const gchar *to = from?"inf":"no";
399 
400 	g_value_set_static_string(to_value, to);
401 
402 	return TRUE;
403 }
404 
405 static void
mpris_enable_handler(GSettings * settings,gchar * key,gpointer data)406 mpris_enable_handler(GSettings *settings, gchar *key, gpointer data)
407 {
408 	CelluloidController *controller = data;
409 
410 	if(!controller->mpris && g_settings_get_boolean(settings, key))
411 	{
412 		controller->mpris = celluloid_mpris_new(controller);
413 	}
414 	else if(controller->mpris)
415 	{
416 		g_clear_object(&controller->mpris);
417 	}
418 }
419 
420 static void
media_keys_enable_handler(GSettings * settings,gchar * key,gpointer data)421 media_keys_enable_handler(GSettings *settings, gchar *key, gpointer data)
422 {
423 	CelluloidController *controller = data;
424 
425 	if(!controller->media_keys && g_settings_get_boolean(settings, key))
426 	{
427 		controller->media_keys = celluloid_media_keys_new(controller);
428 	}
429 	else if(controller->media_keys)
430 	{
431 		g_clear_object(&controller->media_keys);
432 	}
433 }
434 
435 static void
view_ready_handler(CelluloidView * view,gpointer data)436 view_ready_handler(CelluloidView *view, gpointer data)
437 {
438 	CelluloidController *controller = CELLULOID_CONTROLLER(data);
439 	CelluloidModel *model = controller->model;
440 	gboolean maximized = FALSE;
441 
442 	celluloid_player_options_init
443 		(	CELLULOID_PLAYER(controller->model),
444 			CELLULOID_MAIN_WINDOW(controller->view) );
445 	celluloid_model_initialize
446 		(model);
447 
448 	g_object_get
449 		(view, "maximized", &maximized, NULL);
450 	celluloid_mpv_set_property_flag
451 		(CELLULOID_MPV(model), "window-maximized", maximized);
452 }
453 
454 static void
render_handler(CelluloidView * view,gpointer data)455 render_handler(CelluloidView *view, gpointer data)
456 {
457 	CelluloidController *controller = data;
458 	gint scale = 1;
459 	gint width = -1;
460 	gint height = -1;
461 
462 	scale = celluloid_view_get_scale_factor(controller->view);
463 
464 	celluloid_view_get_video_area_geometry
465 		(controller->view, &width, &height);
466 	celluloid_model_render_frame
467 		(controller->model, scale*width, scale*height);
468 }
469 
470 static void
preferences_updated_handler(CelluloidView * view,gpointer data)471 preferences_updated_handler(CelluloidView *view, gpointer data)
472 {
473 	CelluloidController *controller = data;
474 
475 	update_extra_mpv_options(controller);
476 	celluloid_view_make_gl_context_current(controller->view);
477 	celluloid_model_reset(controller->model);
478 }
479 
480 static void
audio_track_load_handler(CelluloidView * view,const gchar * uri,gpointer data)481 audio_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data)
482 {
483 	celluloid_model_load_audio_track(CELLULOID_CONTROLLER(data)->model, uri);
484 }
485 
486 static void
video_track_load_handler(CelluloidView * view,const gchar * uri,gpointer data)487 video_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data)
488 {
489 	celluloid_model_load_video_track(CELLULOID_CONTROLLER(data)->model, uri);
490 }
491 
492 static void
subtitle_track_load_handler(CelluloidView * view,const gchar * uri,gpointer data)493 subtitle_track_load_handler(	CelluloidView *view,
494 				const gchar *uri,
495 				gpointer data )
496 {
497 	celluloid_model_load_subtitle_track
498 		(CELLULOID_CONTROLLER(data)->model, uri);
499 }
500 
501 static void
file_open_handler(CelluloidView * view,GListModel * files,gboolean append,gpointer data)502 file_open_handler(	CelluloidView *view,
503 			GListModel *files,
504 			gboolean append,
505 			gpointer data )
506 {
507 	CelluloidModel *model = CELLULOID_CONTROLLER(data)->model;
508 	guint files_count = g_list_model_get_n_items(files);
509 
510 	for(guint i = 0; i < files_count; i++)
511 	{
512 		GFile *file = g_list_model_get_item(files, i);
513 		gchar *uri = g_file_get_uri(file);
514 
515 		celluloid_model_load_file(model, uri, append || i > 0);
516 
517 		g_free(uri);
518 	}
519 }
520 
521 static void
is_active_handler(GObject * gobject,GParamSpec * pspec,gpointer data)522 is_active_handler(GObject *gobject, GParamSpec *pspec, gpointer data)
523 {
524 	CelluloidController *controller = CELLULOID_CONTROLLER(data);
525 	CelluloidView *view = CELLULOID_VIEW(gobject);
526 	gboolean is_active = TRUE;
527 
528 	g_object_get(view, "is-active", &is_active, NULL);
529 
530 	if(!is_active)
531 	{
532 		celluloid_model_reset_keys(controller->model);
533 	}
534 }
535 
536 static gboolean
close_request_handler(CelluloidView * view,gpointer data)537 close_request_handler(CelluloidView *view, gpointer data)
538 {
539 	g_signal_emit_by_name(data, "shutdown");
540 
541 	return TRUE;
542 }
543 
544 static void
playlist_item_activated_handler(CelluloidView * view,gint pos,gpointer data)545 playlist_item_activated_handler(CelluloidView *view, gint pos, gpointer data)
546 {
547 	CelluloidController *controller = CELLULOID_CONTROLLER(data);
548 	gboolean idle_active = FALSE;
549 
550 	g_object_get(controller->model, "idle-active", &idle_active, NULL);
551 	celluloid_model_play(controller->model);
552 
553 	if(idle_active)
554 	{
555 		controller->target_playlist_pos = pos;
556 	}
557 	else
558 	{
559 		celluloid_model_set_playlist_position(controller->model, pos);
560 	}
561 }
562 
563 static void
playlist_item_inserted_handler(CelluloidView * view,gint pos,gpointer data)564 playlist_item_inserted_handler(CelluloidView *view, gint pos, gpointer data)
565 {
566 	CelluloidModel *model = CELLULOID_CONTROLLER(data)->model;
567 	CelluloidMainWindow *window = CELLULOID_MAIN_WINDOW(view);
568 	CelluloidPlaylistWidget *playlist = celluloid_main_window_get_playlist(window);
569 	GPtrArray *contents = celluloid_playlist_widget_get_contents(playlist);
570 
571 	g_assert(contents->len > 0);
572 	CelluloidPlaylistEntry *entry = g_ptr_array_index(contents, pos);
573 
574 	if((guint)pos != contents->len - 1)
575 	{
576 		g_warning("Playlist item inserted at non-last position. This is not yet supported. Appending to the playlist instead.");
577 	}
578 
579 	celluloid_model_load_file(model, entry->filename, TRUE);
580 }
581 
582 static void
playlist_item_deleted_handler(CelluloidView * view,gint pos,gpointer data)583 playlist_item_deleted_handler(CelluloidView *view, gint pos, gpointer data)
584 {
585 	celluloid_model_remove_playlist_entry
586 		(CELLULOID_CONTROLLER(data)->model, pos);
587 }
588 
589 static void
playlist_reordered_handler(CelluloidView * view,gint src,gint dst,gpointer data)590 playlist_reordered_handler(	CelluloidView *view,
591 				gint src,
592 				gint dst,
593 				gpointer data )
594 {
595 	celluloid_model_move_playlist_entry
596 		(CELLULOID_CONTROLLER(data)->model, src, dst);
597 }
598 
599 static void
set_use_skip_button_for_playlist(CelluloidController * controller,gboolean value)600 set_use_skip_button_for_playlist(	CelluloidController *controller,
601 					gboolean value )
602 {
603 	if(controller->skip_buttons_binding)
604 	{
605 		g_binding_unbind(controller->skip_buttons_binding);
606 	}
607 
608 	controller->skip_buttons_binding
609 		= g_object_bind_property_full
610 			(	controller->model,
611 				value?"playlist-count":"chapters",
612 				controller->view,
613 				"skip-enabled",
614 				G_BINDING_DEFAULT|G_BINDING_SYNC_CREATE,
615 				is_more_than_one,
616 				NULL,
617 				NULL,
618 				NULL );
619 }
620 
621 static void
connect_signals(CelluloidController * controller)622 connect_signals(CelluloidController *controller)
623 {
624 	g_object_bind_property(	controller->model, "core-idle",
625 				controller, "idle",
626 				G_BINDING_DEFAULT );
627 	g_object_bind_property(	controller->model, "border",
628 				controller->view, "border",
629 				G_BINDING_DEFAULT );
630 	g_object_bind_property(	controller->model, "fullscreen",
631 				controller->view, "fullscreen",
632 				G_BINDING_BIDIRECTIONAL );
633 	g_object_bind_property(	controller->model, "window-maximized",
634 				controller->view, "maximized",
635 				G_BINDING_BIDIRECTIONAL );
636 	g_object_bind_property(	controller->model, "pause",
637 				controller->view, "pause",
638 				G_BINDING_DEFAULT );
639 	g_object_bind_property(	controller->model, "idle-active",
640 				controller->view, "idle-active",
641 				G_BINDING_DEFAULT );
642 	g_object_bind_property(	controller->model, "media-title",
643 				controller->view, "media-title",
644 				G_BINDING_DEFAULT );
645 	g_object_bind_property(	controller->model, "volume",
646 				controller->view, "volume",
647 				G_BINDING_BIDIRECTIONAL );
648 	g_object_bind_property(	controller->model, "volume-max",
649 				controller->view, "volume-max",
650 				G_BINDING_DEFAULT );
651 	g_object_bind_property(	controller->model, "duration",
652 				controller->view, "duration",
653 				G_BINDING_DEFAULT );
654 	g_object_bind_property(	controller->model, "playlist-pos",
655 				controller->view, "playlist-pos",
656 				G_BINDING_DEFAULT );
657 	g_object_bind_property(	controller->model, "track-list",
658 				controller->view, "track-list",
659 				G_BINDING_DEFAULT );
660 	g_object_bind_property(	controller->model, "disc-list",
661 				controller->view, "disc-list",
662 				G_BINDING_DEFAULT );
663 	g_object_bind_property_full(	controller->view, "loop",
664 					controller->model, "loop-playlist",
665 					G_BINDING_BIDIRECTIONAL|
666 					G_BINDING_SYNC_CREATE,
667 					boolean_to_loop,
668 					loop_to_boolean,
669 					NULL,
670 					NULL );
671 	g_object_bind_property(	controller->view, "shuffle",
672 				controller->model, "shuffle",
673 				G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE );
674 
675 	g_signal_connect(	controller->model,
676 				"notify::ready",
677 				G_CALLBACK(model_ready_handler),
678 				controller );
679 	g_signal_connect(	controller->model,
680 				"notify::idle-active",
681 				G_CALLBACK(idle_active_handler),
682 				controller );
683 	g_signal_connect(	controller->model,
684 				"notify::playlist",
685 				G_CALLBACK(playlist_handler),
686 				controller );
687 	g_signal_connect(	controller->model,
688 				"notify::vid",
689 				G_CALLBACK(vid_handler),
690 				controller );
691 	g_signal_connect(	controller->model,
692 				"notify::window-scale",
693 				G_CALLBACK(window_scale_handler),
694 				controller );
695 	g_signal_connect(	controller->model,
696 				"playlist-replaced",
697 				G_CALLBACK(playlist_replaced_handler),
698 				controller );
699 	g_signal_connect(	controller->model,
700 				"frame-ready",
701 				G_CALLBACK(frame_ready_handler),
702 				controller );
703 	g_signal_connect(	controller->model,
704 				"metadata-update",
705 				G_CALLBACK(metadata_update_handler),
706 				controller );
707 	g_signal_connect(	controller->model,
708 				"window-resize",
709 				G_CALLBACK(window_resize_handler),
710 				controller );
711 	g_signal_connect(	controller->model,
712 				"window-move",
713 				G_CALLBACK(window_move_handler),
714 				controller );
715 	g_signal_connect(	controller->model,
716 				"message",
717 				G_CALLBACK(message_handler),
718 				controller );
719 	g_signal_connect(	controller->model,
720 				"error",
721 				G_CALLBACK(error_handler),
722 				controller );
723 	g_signal_connect(	controller->model,
724 				"shutdown",
725 				G_CALLBACK(shutdown_handler),
726 				controller );
727 
728 	g_signal_connect(	controller->view,
729 				"video-area-resize",
730 				G_CALLBACK(video_area_resize_handler),
731 				controller );
732 	g_signal_connect(	controller->view,
733 				"notify::fullscreen",
734 				G_CALLBACK(fullscreen_handler),
735 				controller );
736 	g_signal_connect(	controller->view,
737 				"notify::searching",
738 				G_CALLBACK(searching_handler),
739 				controller );
740 	g_signal_connect(	controller->view,
741 				"button-clicked::play",
742 				G_CALLBACK(play_button_handler),
743 				controller );
744 	g_signal_connect(	controller->view,
745 				"button-clicked::stop",
746 				G_CALLBACK(stop_button_handler),
747 				controller );
748 	g_signal_connect(	controller->view,
749 				"button-clicked::forward",
750 				G_CALLBACK(forward_button_handler),
751 				controller );
752 	g_signal_connect(	controller->view,
753 				"button-clicked::rewind",
754 				G_CALLBACK(rewind_button_handler),
755 				controller );
756 	g_signal_connect(	controller->view,
757 				"button-clicked::next",
758 				G_CALLBACK(next_button_handler),
759 				controller );
760 	g_signal_connect(	controller->view,
761 				"button-clicked::previous",
762 				G_CALLBACK(previous_button_handler),
763 				controller );
764 	g_signal_connect(	controller->view,
765 				"button-clicked::fullscreen",
766 				G_CALLBACK(fullscreen_button_handler),
767 				controller );
768 	g_signal_connect(	controller->view,
769 				"seek",
770 				G_CALLBACK(seek_handler),
771 				controller );
772 
773 	g_signal_connect(	controller->view,
774 				"ready",
775 				G_CALLBACK(view_ready_handler),
776 				controller );
777 	g_signal_connect(	controller->view,
778 				"render",
779 				G_CALLBACK(render_handler),
780 				controller );
781 	g_signal_connect(	controller->view,
782 				"preferences-updated",
783 				G_CALLBACK(preferences_updated_handler),
784 				controller );
785 	g_signal_connect(	controller->view,
786 				"audio-track-load",
787 				G_CALLBACK(audio_track_load_handler),
788 				controller );
789 	g_signal_connect(	controller->view,
790 				"video-track-load",
791 				G_CALLBACK(video_track_load_handler),
792 				controller );
793 	g_signal_connect(	controller->view,
794 				"subtitle-track-load",
795 				G_CALLBACK(subtitle_track_load_handler),
796 				controller );
797 	g_signal_connect(	controller->view,
798 				"file-open",
799 				G_CALLBACK(file_open_handler),
800 				controller );
801 	g_signal_connect(	controller->view,
802 				"notify::is-active",
803 				G_CALLBACK(is_active_handler),
804 				controller );
805 	g_signal_connect(	controller->view,
806 				"close-request",
807 				G_CALLBACK(close_request_handler),
808 				controller );
809 	g_signal_connect(	controller->view,
810 				"playlist-item-activated",
811 				G_CALLBACK(playlist_item_activated_handler),
812 				controller );
813 	g_signal_connect(	controller->view,
814 				"playlist-item-inserted",
815 				G_CALLBACK(playlist_item_inserted_handler),
816 				controller );
817 	g_signal_connect(	controller->view,
818 				"playlist-item-deleted",
819 				G_CALLBACK(playlist_item_deleted_handler),
820 				controller );
821 	g_signal_connect(	controller->view,
822 				"playlist-reordered",
823 				G_CALLBACK(playlist_reordered_handler),
824 				controller );
825 }
826 
827 static gboolean
update_seek_bar(gpointer data)828 update_seek_bar(gpointer data)
829 {
830 	CelluloidController *controller = data;
831 	gdouble time_pos = celluloid_model_get_time_position(controller->model);
832 
833 	celluloid_view_set_time_position(controller->view, time_pos);
834 
835 	return TRUE;
836 }
837 
838 static gboolean
is_more_than_one(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer data)839 is_more_than_one(	GBinding *binding,
840 			const GValue *from_value,
841 			GValue *to_value,
842 			gpointer data )
843 {
844 	gint64 from = g_value_get_int64(from_value);
845 
846 	g_value_set_boolean(to_value, from > 1);
847 
848 	return TRUE;
849 }
850 
851 static void
idle_active_handler(GObject * object,GParamSpec * pspec,gpointer data)852 idle_active_handler(GObject *object, GParamSpec *pspec, gpointer data)
853 {
854 	CelluloidController *controller = data;
855 	gboolean idle_active = TRUE;
856 
857 	g_object_get(object, "idle-active", &idle_active, NULL);
858 
859 	if(idle_active)
860 	{
861 		celluloid_view_reset(CELLULOID_CONTROLLER(data)->view);
862 	}
863 	else if(controller->target_playlist_pos >= 0)
864 	{
865 		celluloid_model_set_playlist_position
866 			(controller->model, controller->target_playlist_pos);
867 	}
868 }
869 
870 static void
playlist_handler(GObject * object,GParamSpec * pspec,gpointer data)871 playlist_handler(GObject *object, GParamSpec *pspec, gpointer data)
872 {
873 	CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
874 	GPtrArray *playlist = NULL;
875 	gint64 pos = 0;
876 
877 	g_object_get(	object,
878 			"playlist", &playlist,
879 			"playlist-pos", &pos,
880 			NULL );
881 
882 	celluloid_view_update_playlist(view, playlist);
883 	celluloid_view_set_playlist_pos(view, pos);
884 }
885 
886 static void
vid_handler(GObject * object,GParamSpec * pspec,gpointer data)887 vid_handler(GObject *object, GParamSpec *pspec, gpointer data)
888 {
889 	CelluloidController *controller = data;
890 	CelluloidMainWindow *window =
891 		celluloid_view_get_main_window(controller->view);
892 	GActionMap *map = G_ACTION_MAP(window);
893 	GAction *action = g_action_map_lookup_action(map, "set-video-size");
894 	gchar *vid_str = NULL;
895 	gint64 vid = 0;
896 
897 	// Queue render to clear last frame from the buffer in case video
898 	// becomes disabled
899 	celluloid_view_queue_render(CELLULOID_CONTROLLER(data)->view);
900 
901 	g_object_get(object, "vid", &vid_str, NULL);
902 	vid = g_ascii_strtoll(vid_str, NULL, 10);
903 	g_simple_action_set_enabled(G_SIMPLE_ACTION(action), vid > 0);
904 
905 	g_free(vid_str);
906 }
907 
908 static void
window_scale_handler(GObject * object,GParamSpec * pspec,gpointer data)909 window_scale_handler(GObject *object, GParamSpec *pspec, gpointer data)
910 {
911 	CelluloidController *controller = data;
912 	gint64 video_width = 1;
913 	gint64 video_height = 1;
914 	gint width = 1;
915 	gint height = 1;
916 	gdouble window_scale = 1.0;
917 	gdouble new_window_scale = 1.0;
918 
919 	celluloid_model_get_video_geometry
920 		(controller->model, &video_width, &video_height);
921 	celluloid_view_get_video_area_geometry
922 		(controller->view, &width, &height);
923 
924 	g_object_get(object, "window-scale", &new_window_scale, NULL);
925 
926 	window_scale = MIN(	width/(gdouble)video_width,
927 				height/(gdouble)video_height );
928 
929 	if(window_scale > 0.0 && ABS(window_scale-new_window_scale) > 0.0001)
930 	{
931 		celluloid_controller_autofit(data, new_window_scale);
932 	}
933 }
934 
935 static void
model_ready_handler(GObject * object,GParamSpec * pspec,gpointer data)936 model_ready_handler(GObject *object, GParamSpec *pspec, gpointer data)
937 {
938 	CelluloidController *controller = data;
939 	gboolean ready = FALSE;
940 
941 	g_object_get(object, "ready", &ready, NULL);
942 
943 	if(ready)
944 	{
945 		gboolean use_opengl_cb;
946 
947 		use_opengl_cb =	celluloid_model_get_use_opengl_cb
948 				(controller->model);
949 
950 		celluloid_view_set_use_opengl_cb
951 			(controller->view, use_opengl_cb);
952 
953 		if(use_opengl_cb)
954 		{
955 			celluloid_view_make_gl_context_current(controller->view);
956 			celluloid_model_initialize_gl(controller->model);
957 		}
958 	}
959 
960 	g_source_clear(&controller->update_seekbar_id);
961 	controller->update_seekbar_id
962 		= g_timeout_add(	SEEK_BAR_UPDATE_INTERVAL,
963 					(GSourceFunc)update_seek_bar,
964 					controller );
965 
966 	controller->ready = ready;
967 	g_object_notify(data, "ready");
968 }
969 
970 static void
playlist_replaced_handler(CelluloidModel * model,gpointer data)971 playlist_replaced_handler(CelluloidModel *model, gpointer data)
972 {
973 	GSettings *settings = g_settings_new(CONFIG_ROOT);
974 
975 	if(g_settings_get_boolean(settings, "present-window-on-file-open"))
976 	{
977 		celluloid_view_present(CELLULOID_CONTROLLER(data)->view);
978 	}
979 
980 	g_object_unref(settings);
981 }
982 
983 static void
frame_ready_handler(CelluloidModel * model,gpointer data)984 frame_ready_handler(CelluloidModel *model, gpointer data)
985 {
986 	celluloid_view_queue_render(CELLULOID_CONTROLLER(data)->view);
987 }
988 
989 static void
metadata_update_handler(CelluloidModel * model,gint64 pos,gpointer data)990 metadata_update_handler(CelluloidModel *model, gint64 pos, gpointer data)
991 {
992 	CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
993 	GPtrArray *playlist = NULL;
994 
995 	g_object_get(G_OBJECT(model), "playlist", &playlist, NULL);
996 	celluloid_view_update_playlist(view, playlist);
997 }
998 
999 static void
window_resize_handler(CelluloidModel * model,gint64 width,gint64 height,gpointer data)1000 window_resize_handler(	CelluloidModel *model,
1001 			gint64 width,
1002 			gint64 height,
1003 			gpointer data )
1004 {
1005 	CelluloidController *controller = data;
1006 
1007 	celluloid_view_resize_video_area(controller->view, (gint)width, (gint)height);
1008 }
1009 
1010 static void
window_move_handler(CelluloidModel * model,gboolean flip_x,gboolean flip_y,GValue * x,GValue * y,gpointer data)1011 window_move_handler(	CelluloidModel *model,
1012 			gboolean flip_x,
1013 			gboolean flip_y,
1014 			GValue *x,
1015 			GValue *y,
1016 			gpointer data )
1017 {
1018 	celluloid_view_move
1019 		(CELLULOID_CONTROLLER(data)->view, flip_x, flip_y, x, y);
1020 }
1021 
1022 static void
message_handler(CelluloidMpv * mpv,const gchar * message,gpointer data)1023 message_handler(CelluloidMpv *mpv, const gchar *message, gpointer data)
1024 {
1025 	const gsize prefix_length = sizeof(ACTION_PREFIX)-1;
1026 
1027 	/* Verify that both the prefix and the scope matches */
1028 	if(message && strncmp(message, ACTION_PREFIX, prefix_length) == 0)
1029 	{
1030 		const gchar *action = message+prefix_length+1;
1031 		CelluloidController *controller = data;
1032 		CelluloidView *view = controller->view;
1033 		GActionMap *map = NULL;
1034 
1035 		if(g_str_has_prefix(action, "win."))
1036 		{
1037 			map = G_ACTION_MAP(celluloid_view_get_main_window(view));
1038 		}
1039 		else if(g_str_has_prefix(action, "app."))
1040 		{
1041 			map = G_ACTION_MAP(controller->app);
1042 		}
1043 
1044 		if(map)
1045 		{
1046 			/* Strip scope and activate */
1047 			activate_action_string(map, strchr(action, '.')+1);
1048 		}
1049 		else
1050 		{
1051 			g_warning(	"Received action with missing or "
1052 					"unknown scope %s",
1053 					action );
1054 		}
1055 	}
1056 }
1057 
1058 static void
error_handler(CelluloidMpv * mpv,const gchar * message,gpointer data)1059 error_handler(CelluloidMpv *mpv, const gchar *message, gpointer data)
1060 {
1061 	celluloid_view_show_message_dialog
1062 		(	CELLULOID_CONTROLLER(data)->view,
1063 			GTK_MESSAGE_ERROR,
1064 			_("Error"),
1065 			NULL,
1066 			message );
1067 }
1068 
1069 static void
shutdown_handler(CelluloidMpv * mpv,gpointer data)1070 shutdown_handler(CelluloidMpv *mpv, gpointer data)
1071 {
1072 	g_signal_emit_by_name(data, "shutdown");
1073 }
1074 
1075 static gboolean
update_window_scale(gpointer data)1076 update_window_scale(gpointer data)
1077 {
1078 	gpointer *params = data;
1079 	CelluloidController *controller = CELLULOID_CONTROLLER(params[0]);
1080 	gint64 video_width = 1;
1081 	gint64 video_height = 1;
1082 	gint width = 1;
1083 	gint height = 1;
1084 	gdouble window_scale = 0;
1085 	gdouble new_window_scale = 0;
1086 
1087 	celluloid_model_get_video_geometry
1088 		(controller->model, &video_width, &video_height);
1089 	celluloid_view_get_video_area_geometry
1090 		(controller->view, &width, &height);
1091 
1092 	new_window_scale = MIN(	width/(gdouble)video_width,
1093 				height/(gdouble)video_height );
1094 	g_object_get(controller->model, "window-scale", &window_scale, NULL);
1095 
1096 	if(ABS(window_scale-new_window_scale) > 0.0001)
1097 	{
1098 		g_object_set(	controller->model,
1099 				"window-scale",
1100 				new_window_scale,
1101 				NULL );
1102 	}
1103 
1104 	// Clear event source ID for the timeout
1105 	*((guint *)params[1]) = 0;
1106 	g_free(params);
1107 
1108 	return FALSE;
1109 }
1110 
1111 static void
video_area_resize_handler(CelluloidView * view,gint width,gint height,gpointer data)1112 video_area_resize_handler(	CelluloidView *view,
1113 				gint width,
1114 				gint height,
1115 				gpointer data )
1116 {
1117 	CelluloidController *controller = data;
1118 	gpointer *params = g_new(gpointer, 2);
1119 
1120 	g_source_clear(&controller->resize_timeout_tag);
1121 
1122 	params[0] = data;
1123 	params[1] = &(controller->resize_timeout_tag);
1124 
1125 	// Rate-limit the call to update_window_scale(), which will update the
1126 	// window-scale property in the model, to no more than once every 250ms.
1127 	controller->resize_timeout_tag = g_timeout_add(	250,
1128 							update_window_scale,
1129 							params );
1130 }
1131 
1132 static void
fullscreen_handler(GObject * object,GParamSpec * pspec,gpointer data)1133 fullscreen_handler(GObject *object, GParamSpec *pspec, gpointer data)
1134 {
1135 	CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
1136 	CelluloidMainWindow *window = celluloid_view_get_main_window(view);
1137 	GActionMap *map = G_ACTION_MAP(window);
1138 	GAction *toggle_playlist = NULL;
1139 	gboolean fullscreen = FALSE;
1140 
1141 	toggle_playlist = g_action_map_lookup_action(map, "toggle-playlist");
1142 
1143 	g_object_get(view, "fullscreen", &fullscreen, NULL);
1144 
1145 	g_simple_action_set_enabled
1146 		(G_SIMPLE_ACTION(toggle_playlist), !fullscreen);
1147 }
1148 
1149 static void
searching_handler(GObject * object,GParamSpec * pspec,gpointer data)1150 searching_handler(GObject *object, GParamSpec *pspec, gpointer data)
1151 {
1152 	// When the search box becomes visible, it blocks all keyboard inputs
1153 	// from being handled by CelluloidController. This means that the key up
1154 	// event for the key that triggered the search will never arrive, so we
1155 	// need to explicitly reset key states here.
1156 	celluloid_model_reset_keys(CELLULOID_CONTROLLER(data)->model);
1157 }
1158 
1159 static void
play_button_handler(GtkButton * button,gpointer data)1160 play_button_handler(GtkButton *button, gpointer data)
1161 {
1162 	CelluloidModel *model = CELLULOID_CONTROLLER(data)->model;
1163 	gboolean pause = TRUE;
1164 
1165 	g_object_get(model, "pause", &pause, NULL);
1166 
1167 	if(pause)
1168 	{
1169 		celluloid_model_play(model);
1170 	}
1171 	else
1172 	{
1173 		celluloid_model_pause(model);
1174 	}
1175 }
1176 
1177 static void
stop_button_handler(GtkButton * button,gpointer data)1178 stop_button_handler(GtkButton *button, gpointer data)
1179 {
1180 	celluloid_model_stop(CELLULOID_CONTROLLER(data)->model);
1181 }
1182 
1183 static void
forward_button_handler(GtkButton * button,gpointer data)1184 forward_button_handler(GtkButton *button, gpointer data)
1185 {
1186 	celluloid_model_forward(CELLULOID_CONTROLLER(data)->model);
1187 }
1188 
1189 static void
rewind_button_handler(GtkButton * button,gpointer data)1190 rewind_button_handler(GtkButton *button, gpointer data)
1191 {
1192 	celluloid_model_rewind(CELLULOID_CONTROLLER(data)->model);
1193 }
1194 
1195 static void
next_button_handler(GtkButton * button,gpointer data)1196 next_button_handler(GtkButton *button, gpointer data)
1197 {
1198 	CelluloidController *controller = data;
1199 
1200 	if(controller->use_skip_buttons_for_playlist)
1201 	{
1202 		celluloid_model_next_playlist_entry
1203 			(CELLULOID_CONTROLLER(data)->model);
1204 	}
1205 	else
1206 	{
1207 		celluloid_model_next_chapter
1208 			(CELLULOID_CONTROLLER(data)->model);
1209 	}
1210 }
1211 
1212 static void
previous_button_handler(GtkButton * button,gpointer data)1213 previous_button_handler(GtkButton *button, gpointer data)
1214 {
1215 	CelluloidController *controller = data;
1216 
1217 	if(controller->use_skip_buttons_for_playlist)
1218 	{
1219 		celluloid_model_previous_playlist_entry
1220 			(CELLULOID_CONTROLLER(data)->model);
1221 	}
1222 	else
1223 	{
1224 		celluloid_model_previous_chapter
1225 			(CELLULOID_CONTROLLER(data)->model);
1226 	}
1227 }
1228 
1229 static void
fullscreen_button_handler(GtkButton * button,gpointer data)1230 fullscreen_button_handler(GtkButton *button, gpointer data)
1231 {
1232 	CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
1233 	gboolean fullscreen = FALSE;
1234 
1235 	g_object_get(view, "fullscreen", &fullscreen, NULL);
1236 	g_object_set(view, "fullscreen", !fullscreen, NULL);
1237 }
1238 
1239 static void
seek_handler(GtkButton * button,gdouble value,gpointer data)1240 seek_handler(GtkButton *button, gdouble value, gpointer data)
1241 {
1242 	celluloid_model_seek(CELLULOID_CONTROLLER(data)->model, value);
1243 }
1244 
1245 static void
celluloid_controller_class_init(CelluloidControllerClass * klass)1246 celluloid_controller_class_init(CelluloidControllerClass *klass)
1247 {
1248 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
1249 	GParamSpec *pspec;
1250 
1251 	obj_class->constructed = constructed;
1252 	obj_class->set_property = set_property;
1253 	obj_class->get_property = get_property;
1254 	obj_class->dispose = dispose;
1255 
1256 	pspec = g_param_spec_pointer
1257 		(	"app",
1258 			"App",
1259 			"The CelluloidApplication to use",
1260 			G_PARAM_CONSTRUCT_ONLY|G_PARAM_READWRITE );
1261 	g_object_class_install_property(obj_class, PROP_APP, pspec);
1262 
1263 	pspec = g_param_spec_boolean
1264 		(	"ready",
1265 			"Ready",
1266 			"Whether mpv is ready to receive commands",
1267 			FALSE,
1268 			G_PARAM_READWRITE );
1269 	g_object_class_install_property(obj_class, PROP_READY, pspec);
1270 
1271 	pspec = g_param_spec_boolean
1272 		(	"idle",
1273 			"Idle",
1274 			"Whether or not the player is idle",
1275 			TRUE,
1276 			G_PARAM_READWRITE );
1277 	g_object_class_install_property(obj_class, PROP_IDLE, pspec);
1278 
1279 	pspec = g_param_spec_boolean
1280 		(	"use-skip-buttons-for-playlist",
1281 			"Use skip buttons for playlist",
1282 			"Whether or not to use the skip buttons to control the playlist",
1283 			FALSE,
1284 			G_PARAM_READWRITE );
1285 	g_object_class_install_property(obj_class, PROP_USE_SKIP_BUTTONS_FOR_PLAYLIST, pspec);
1286 
1287 	g_signal_new(	"shutdown",
1288 			G_TYPE_FROM_CLASS(klass),
1289 			G_SIGNAL_RUN_FIRST,
1290 			0,
1291 			NULL,
1292 			NULL,
1293 			g_cclosure_marshal_VOID__VOID,
1294 			G_TYPE_NONE,
1295 			0 );
1296 }
1297 
1298 static void
celluloid_controller_init(CelluloidController * controller)1299 celluloid_controller_init(CelluloidController *controller)
1300 {
1301 	controller->key_controller = gtk_event_controller_key_new();
1302 
1303 	controller->app = NULL;
1304 	controller->model = NULL;
1305 	controller->view = NULL;
1306 	controller->ready = FALSE;
1307 	controller->idle = TRUE;
1308 	controller->target_playlist_pos = -1;
1309 	controller->update_seekbar_id = 0;
1310 	controller->resize_timeout_tag = 0;
1311 	controller->skip_buttons_binding = NULL;
1312 	controller->settings = g_settings_new(CONFIG_ROOT);
1313 	controller->media_keys = NULL;
1314 	controller->mpris = NULL;
1315 }
1316 
1317 static void
update_extra_mpv_options(CelluloidController * controller)1318 update_extra_mpv_options(CelluloidController *controller)
1319 {
1320 	GSettings *settings =		g_settings_new(CONFIG_ROOT);
1321 	const gchar *cli_options =	celluloid_application_get_mpv_options
1322 					(controller->app);
1323 	gchar *pref_options =		g_settings_get_string(settings, "mpv-options");
1324 	gchar *options =		g_strjoin(" ", pref_options, cli_options, NULL);
1325 
1326 
1327 	g_object_set(controller->model, "extra-options", options, NULL);
1328 
1329 	g_free(options);
1330 	g_free(pref_options);
1331 	g_object_unref(settings);
1332 }
1333 
1334 CelluloidController *
celluloid_controller_new(CelluloidApplication * app)1335 celluloid_controller_new(CelluloidApplication *app)
1336 {
1337 	const GType type = celluloid_controller_get_type();
1338 
1339 	return CELLULOID_CONTROLLER(g_object_new(type, "app", app, NULL));
1340 }
1341 
1342 void
celluloid_controller_quit(CelluloidController * controller)1343 celluloid_controller_quit(CelluloidController *controller)
1344 {
1345 	celluloid_view_quit(controller->view);
1346 }
1347 
1348 void
celluloid_controller_autofit(CelluloidController * controller,gdouble multiplier)1349 celluloid_controller_autofit(	CelluloidController *controller,
1350 				gdouble multiplier )
1351 {
1352 	gchar *vid = NULL;
1353 	gint64 width = -1;
1354 	gint64 height = -1;
1355 
1356 	g_object_get(G_OBJECT(controller->model), "vid", &vid, NULL);
1357 	celluloid_model_get_video_geometry(controller->model, &width, &height);
1358 
1359 	if(vid && strncmp(vid, "no", 3) != 0 && width >= 0 && width >= 0)
1360 	{
1361 		gint new_width = (gint)(multiplier*(gdouble)width);
1362 		gint new_height = (gint)(multiplier*(gdouble)height);
1363 		gint scale = celluloid_view_get_scale_factor(controller->view);
1364 
1365 		g_debug("Resizing window to %dx%d", new_width, new_height);
1366 		celluloid_view_resize_video_area(	controller->view,
1367 							new_width/scale,
1368 							new_height/scale );
1369 	}
1370 
1371 	g_free(vid);
1372 }
1373 
1374 void
celluloid_controller_present(CelluloidController * controller)1375 celluloid_controller_present(CelluloidController *controller)
1376 {
1377 	celluloid_view_present(controller->view);
1378 }
1379 
1380 void
celluloid_controller_open(CelluloidController * controller,const gchar * uri,gboolean append)1381 celluloid_controller_open(	CelluloidController *controller,
1382 				const gchar *uri,
1383 				gboolean append )
1384 {
1385 	celluloid_model_load_file(controller->model, uri, append);
1386 }
1387 
1388 CelluloidView *
celluloid_controller_get_view(CelluloidController * controller)1389 celluloid_controller_get_view(CelluloidController *controller)
1390 {
1391 	return controller->view;
1392 }
1393 
1394 CelluloidModel *
celluloid_controller_get_model(CelluloidController * controller)1395 celluloid_controller_get_model(CelluloidController *controller)
1396 {
1397 	return controller->model;
1398 }
1399