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