/*
* Copyright (c) 2017-2022 gnome-mpv
*
* This file is part of Celluloid.
*
* Celluloid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Celluloid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Celluloid. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "celluloid-controller-private.h"
#include "celluloid-controller.h"
#include "celluloid-controller-actions.h"
#include "celluloid-controller-input.h"
#include "celluloid-player-options.h"
#include "celluloid-def.h"
static void
constructed(GObject *object);
static void
set_property( GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec );
static void
get_property( GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec );
static void
dispose(GObject *object);
static gboolean
loop_to_boolean( GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data );
static gboolean
boolean_to_loop( GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data );
static void
mpris_enable_handler(GSettings *settings, gchar *key, gpointer data);
static void
media_keys_enable_handler(GSettings *settings, gchar *key, gpointer data);
static void
view_ready_handler(CelluloidView *view, gpointer data);
static void
render_handler(CelluloidView *view, gpointer data);
static void
preferences_updated_handler(CelluloidView *view, gpointer data);
static void
audio_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data);
static void
video_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data);
static void
subtitle_track_load_handler( CelluloidView *view,
const gchar *uri,
gpointer data );
static void
file_open_handler( CelluloidView *view,
GListModel *files,
gboolean append,
gpointer data );
static void
is_active_handler(GObject *gobject, GParamSpec *pspec, gpointer data);
static gboolean
close_request_handler(CelluloidView *view, gpointer data);
static void
playlist_item_activated_handler(CelluloidView *view, gint pos, gpointer data);
static void
playlist_item_inserted_handler(CelluloidView *view, gint pos, gpointer data);
static void
playlist_item_deleted_handler(CelluloidView *view, gint pos, gpointer data);
static void
playlist_reordered_handler( CelluloidView *view,
gint src,
gint dst,
gpointer data );
static void
set_use_skip_button_for_playlist( CelluloidController *controller,
gboolean value);
static void
connect_signals(CelluloidController *controller);
static gboolean
update_seek_bar(gpointer data);
static gboolean
is_more_than_one( GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data );
static void
idle_active_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
playlist_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
vid_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
window_scale_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
model_ready_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
playlist_replaced_handler(CelluloidModel *model, gpointer data);
static void
frame_ready_handler(CelluloidModel *model, gpointer data);
static void
metadata_update_handler(CelluloidModel *model, gint64 pos, gpointer data);
static void
window_resize_handler( CelluloidModel *model,
gint64 width,
gint64 height,
gpointer data );
static void
window_move_handler( CelluloidModel *model,
gboolean flip_x,
gboolean flip_y,
GValue *x,
GValue *y,
gpointer data );
static void
message_handler(CelluloidMpv *mpv, const gchar *message, gpointer data);
static void
error_handler(CelluloidMpv *mpv, const gchar *message, gpointer data);
static void
shutdown_handler(CelluloidMpv *mpv, gpointer data);
static gboolean
update_window_scale(gpointer data);
static void
video_area_resize_handler( CelluloidView *view,
gint width,
gint height,
gpointer data );
static void
searching_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
fullscreen_handler(GObject *object, GParamSpec *pspec, gpointer data);
static void
play_button_handler(GtkButton *button, gpointer data);
static void
stop_button_handler(GtkButton *button, gpointer data);
static void
forward_button_handler(GtkButton *button, gpointer data);
static void
rewind_button_handler(GtkButton *button, gpointer data);
static void
next_button_handler(GtkButton *button, gpointer data);
static void
previous_button_handler(GtkButton *button, gpointer data);
static void
fullscreen_button_handler(GtkButton *button, gpointer data);
static void
seek_handler(GtkButton *button, gdouble value, gpointer data);
static void
celluloid_controller_class_init(CelluloidControllerClass *klass);
static void
celluloid_controller_init(CelluloidController *controller);
static void
update_extra_mpv_options(CelluloidController *controller);
G_DEFINE_TYPE(CelluloidController, celluloid_controller, G_TYPE_OBJECT)
static void
constructed(GObject *object)
{
CelluloidController *controller;
CelluloidMainWindow *window;
CelluloidVideoArea *video_area;
gboolean always_floating;
gint64 wid;
controller = CELLULOID_CONTROLLER(object);
always_floating = g_settings_get_boolean
( controller->settings,
"always-use-floating-controls" );
controller->view = celluloid_view_new(controller->app, always_floating);
window = CELLULOID_MAIN_WINDOW(controller->view);
video_area = celluloid_main_window_get_video_area(window);
wid = celluloid_video_area_get_xid(video_area);
controller->model = celluloid_model_new(wid);
connect_signals(controller);
celluloid_controller_action_register_actions(controller);
celluloid_controller_input_connect_signals(controller);
update_extra_mpv_options(controller);
// If the window is already realized at this point (happens on X11), the
// ready signal will never fire. Because of this, we need to manually
// call view_ready_handler() to ensure that the model gets initialized.
if(gtk_widget_get_realized(GTK_WIDGET(window)))
{
view_ready_handler(controller->view, controller);
}
gtk_widget_add_controller(GTK_WIDGET(window), controller->key_controller);
gtk_widget_show(GTK_WIDGET(window));
g_signal_connect( controller->settings,
"changed::mpris-enable",
G_CALLBACK(mpris_enable_handler),
controller );
g_signal_connect( controller->settings,
"changed::media-keys-enable",
G_CALLBACK(media_keys_enable_handler),
controller );
if(g_settings_get_boolean(controller->settings, "mpris-enable"))
{
controller->mpris = celluloid_mpris_new(controller);
}
if(g_settings_get_boolean(controller->settings, "media-keys-enable"))
{
controller->media_keys = celluloid_media_keys_new(controller);
}
G_OBJECT_CLASS(celluloid_controller_parent_class)->constructed(object);
}
static void
set_property( GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec )
{
CelluloidController *self = CELLULOID_CONTROLLER(object);
switch(property_id)
{
case PROP_APP:
self->app = g_value_get_pointer(value);
break;
case PROP_READY:
self->ready = g_value_get_boolean(value);
break;
case PROP_IDLE:
self->idle = g_value_get_boolean(value);
break;
case PROP_USE_SKIP_BUTTONS_FOR_PLAYLIST:
self->use_skip_buttons_for_playlist = g_value_get_boolean(value);
set_use_skip_button_for_playlist
(self, self->use_skip_buttons_for_playlist);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
static void
get_property( GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec )
{
CelluloidController *self = CELLULOID_CONTROLLER(object);
switch(property_id)
{
case PROP_APP:
g_value_set_pointer(value, self->app);
break;
case PROP_READY:
g_value_set_boolean(value, self->ready);
break;
case PROP_IDLE:
g_value_set_boolean(value, self->idle);
break;
case PROP_USE_SKIP_BUTTONS_FOR_PLAYLIST:
g_value_set_boolean(value, self->use_skip_buttons_for_playlist);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
static void
dispose(GObject *object)
{
CelluloidController *controller = CELLULOID_CONTROLLER(object);
g_clear_object(&controller->settings);
g_clear_object(&controller->mpris);
g_clear_object(&controller->media_keys);
g_source_clear(&controller->update_seekbar_id);
g_source_clear(&controller->resize_timeout_tag);
if(controller->view)
{
celluloid_view_make_gl_context_current(controller->view);
g_clear_object(&controller->model);
g_object_unref(controller->view);
controller->view = NULL;
}
G_OBJECT_CLASS(celluloid_controller_parent_class)->dispose(object);
}
static gboolean
loop_to_boolean( GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data )
{
const gchar *from = g_value_get_string(from_value);
gboolean to = g_strcmp0(from, "no") != 0;
g_value_set_boolean(to_value, to);
return TRUE;
}
static gboolean
boolean_to_loop( GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data )
{
gboolean from = g_value_get_boolean(from_value);
const gchar *to = from?"inf":"no";
g_value_set_static_string(to_value, to);
return TRUE;
}
static void
mpris_enable_handler(GSettings *settings, gchar *key, gpointer data)
{
CelluloidController *controller = data;
if(!controller->mpris && g_settings_get_boolean(settings, key))
{
controller->mpris = celluloid_mpris_new(controller);
}
else if(controller->mpris)
{
g_clear_object(&controller->mpris);
}
}
static void
media_keys_enable_handler(GSettings *settings, gchar *key, gpointer data)
{
CelluloidController *controller = data;
if(!controller->media_keys && g_settings_get_boolean(settings, key))
{
controller->media_keys = celluloid_media_keys_new(controller);
}
else if(controller->media_keys)
{
g_clear_object(&controller->media_keys);
}
}
static void
view_ready_handler(CelluloidView *view, gpointer data)
{
CelluloidController *controller = CELLULOID_CONTROLLER(data);
CelluloidModel *model = controller->model;
gboolean maximized = FALSE;
celluloid_player_options_init
( CELLULOID_PLAYER(controller->model),
CELLULOID_MAIN_WINDOW(controller->view) );
celluloid_model_initialize
(model);
g_object_get
(view, "maximized", &maximized, NULL);
celluloid_mpv_set_property_flag
(CELLULOID_MPV(model), "window-maximized", maximized);
}
static void
render_handler(CelluloidView *view, gpointer data)
{
CelluloidController *controller = data;
gint scale = 1;
gint width = -1;
gint height = -1;
scale = celluloid_view_get_scale_factor(controller->view);
celluloid_view_get_video_area_geometry
(controller->view, &width, &height);
celluloid_model_render_frame
(controller->model, scale*width, scale*height);
}
static void
preferences_updated_handler(CelluloidView *view, gpointer data)
{
CelluloidController *controller = data;
update_extra_mpv_options(controller);
celluloid_view_make_gl_context_current(controller->view);
celluloid_model_reset(controller->model);
}
static void
audio_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data)
{
celluloid_model_load_audio_track(CELLULOID_CONTROLLER(data)->model, uri);
}
static void
video_track_load_handler(CelluloidView *view, const gchar *uri, gpointer data)
{
celluloid_model_load_video_track(CELLULOID_CONTROLLER(data)->model, uri);
}
static void
subtitle_track_load_handler( CelluloidView *view,
const gchar *uri,
gpointer data )
{
celluloid_model_load_subtitle_track
(CELLULOID_CONTROLLER(data)->model, uri);
}
static void
file_open_handler( CelluloidView *view,
GListModel *files,
gboolean append,
gpointer data )
{
CelluloidModel *model = CELLULOID_CONTROLLER(data)->model;
guint files_count = g_list_model_get_n_items(files);
for(guint i = 0; i < files_count; i++)
{
GFile *file = g_list_model_get_item(files, i);
gchar *uri = g_file_get_uri(file);
celluloid_model_load_file(model, uri, append || i > 0);
g_free(uri);
}
}
static void
is_active_handler(GObject *gobject, GParamSpec *pspec, gpointer data)
{
CelluloidController *controller = CELLULOID_CONTROLLER(data);
CelluloidView *view = CELLULOID_VIEW(gobject);
gboolean is_active = TRUE;
g_object_get(view, "is-active", &is_active, NULL);
if(!is_active)
{
celluloid_model_reset_keys(controller->model);
}
}
static gboolean
close_request_handler(CelluloidView *view, gpointer data)
{
g_signal_emit_by_name(data, "shutdown");
return TRUE;
}
static void
playlist_item_activated_handler(CelluloidView *view, gint pos, gpointer data)
{
CelluloidController *controller = CELLULOID_CONTROLLER(data);
gboolean idle_active = FALSE;
g_object_get(controller->model, "idle-active", &idle_active, NULL);
celluloid_model_play(controller->model);
if(idle_active)
{
controller->target_playlist_pos = pos;
}
else
{
celluloid_model_set_playlist_position(controller->model, pos);
}
}
static void
playlist_item_inserted_handler(CelluloidView *view, gint pos, gpointer data)
{
CelluloidModel *model = CELLULOID_CONTROLLER(data)->model;
CelluloidMainWindow *window = CELLULOID_MAIN_WINDOW(view);
CelluloidPlaylistWidget *playlist = celluloid_main_window_get_playlist(window);
GPtrArray *contents = celluloid_playlist_widget_get_contents(playlist);
g_assert(contents->len > 0);
CelluloidPlaylistEntry *entry = g_ptr_array_index(contents, pos);
if((guint)pos != contents->len - 1)
{
g_warning("Playlist item inserted at non-last position. This is not yet supported. Appending to the playlist instead.");
}
celluloid_model_load_file(model, entry->filename, TRUE);
}
static void
playlist_item_deleted_handler(CelluloidView *view, gint pos, gpointer data)
{
celluloid_model_remove_playlist_entry
(CELLULOID_CONTROLLER(data)->model, pos);
}
static void
playlist_reordered_handler( CelluloidView *view,
gint src,
gint dst,
gpointer data )
{
celluloid_model_move_playlist_entry
(CELLULOID_CONTROLLER(data)->model, src, dst);
}
static void
set_use_skip_button_for_playlist( CelluloidController *controller,
gboolean value )
{
if(controller->skip_buttons_binding)
{
g_binding_unbind(controller->skip_buttons_binding);
}
controller->skip_buttons_binding
= g_object_bind_property_full
( controller->model,
value?"playlist-count":"chapters",
controller->view,
"skip-enabled",
G_BINDING_DEFAULT|G_BINDING_SYNC_CREATE,
is_more_than_one,
NULL,
NULL,
NULL );
}
static void
connect_signals(CelluloidController *controller)
{
g_object_bind_property( controller->model, "core-idle",
controller, "idle",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "border",
controller->view, "border",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "fullscreen",
controller->view, "fullscreen",
G_BINDING_BIDIRECTIONAL );
g_object_bind_property( controller->model, "window-maximized",
controller->view, "maximized",
G_BINDING_BIDIRECTIONAL );
g_object_bind_property( controller->model, "pause",
controller->view, "pause",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "idle-active",
controller->view, "idle-active",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "media-title",
controller->view, "media-title",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "volume",
controller->view, "volume",
G_BINDING_BIDIRECTIONAL );
g_object_bind_property( controller->model, "volume-max",
controller->view, "volume-max",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "duration",
controller->view, "duration",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "playlist-pos",
controller->view, "playlist-pos",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "track-list",
controller->view, "track-list",
G_BINDING_DEFAULT );
g_object_bind_property( controller->model, "disc-list",
controller->view, "disc-list",
G_BINDING_DEFAULT );
g_object_bind_property_full( controller->view, "loop",
controller->model, "loop-playlist",
G_BINDING_BIDIRECTIONAL|
G_BINDING_SYNC_CREATE,
boolean_to_loop,
loop_to_boolean,
NULL,
NULL );
g_object_bind_property( controller->view, "shuffle",
controller->model, "shuffle",
G_BINDING_BIDIRECTIONAL|G_BINDING_SYNC_CREATE );
g_signal_connect( controller->model,
"notify::ready",
G_CALLBACK(model_ready_handler),
controller );
g_signal_connect( controller->model,
"notify::idle-active",
G_CALLBACK(idle_active_handler),
controller );
g_signal_connect( controller->model,
"notify::playlist",
G_CALLBACK(playlist_handler),
controller );
g_signal_connect( controller->model,
"notify::vid",
G_CALLBACK(vid_handler),
controller );
g_signal_connect( controller->model,
"notify::window-scale",
G_CALLBACK(window_scale_handler),
controller );
g_signal_connect( controller->model,
"playlist-replaced",
G_CALLBACK(playlist_replaced_handler),
controller );
g_signal_connect( controller->model,
"frame-ready",
G_CALLBACK(frame_ready_handler),
controller );
g_signal_connect( controller->model,
"metadata-update",
G_CALLBACK(metadata_update_handler),
controller );
g_signal_connect( controller->model,
"window-resize",
G_CALLBACK(window_resize_handler),
controller );
g_signal_connect( controller->model,
"window-move",
G_CALLBACK(window_move_handler),
controller );
g_signal_connect( controller->model,
"message",
G_CALLBACK(message_handler),
controller );
g_signal_connect( controller->model,
"error",
G_CALLBACK(error_handler),
controller );
g_signal_connect( controller->model,
"shutdown",
G_CALLBACK(shutdown_handler),
controller );
g_signal_connect( controller->view,
"video-area-resize",
G_CALLBACK(video_area_resize_handler),
controller );
g_signal_connect( controller->view,
"notify::fullscreen",
G_CALLBACK(fullscreen_handler),
controller );
g_signal_connect( controller->view,
"notify::searching",
G_CALLBACK(searching_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::play",
G_CALLBACK(play_button_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::stop",
G_CALLBACK(stop_button_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::forward",
G_CALLBACK(forward_button_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::rewind",
G_CALLBACK(rewind_button_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::next",
G_CALLBACK(next_button_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::previous",
G_CALLBACK(previous_button_handler),
controller );
g_signal_connect( controller->view,
"button-clicked::fullscreen",
G_CALLBACK(fullscreen_button_handler),
controller );
g_signal_connect( controller->view,
"seek",
G_CALLBACK(seek_handler),
controller );
g_signal_connect( controller->view,
"ready",
G_CALLBACK(view_ready_handler),
controller );
g_signal_connect( controller->view,
"render",
G_CALLBACK(render_handler),
controller );
g_signal_connect( controller->view,
"preferences-updated",
G_CALLBACK(preferences_updated_handler),
controller );
g_signal_connect( controller->view,
"audio-track-load",
G_CALLBACK(audio_track_load_handler),
controller );
g_signal_connect( controller->view,
"video-track-load",
G_CALLBACK(video_track_load_handler),
controller );
g_signal_connect( controller->view,
"subtitle-track-load",
G_CALLBACK(subtitle_track_load_handler),
controller );
g_signal_connect( controller->view,
"file-open",
G_CALLBACK(file_open_handler),
controller );
g_signal_connect( controller->view,
"notify::is-active",
G_CALLBACK(is_active_handler),
controller );
g_signal_connect( controller->view,
"close-request",
G_CALLBACK(close_request_handler),
controller );
g_signal_connect( controller->view,
"playlist-item-activated",
G_CALLBACK(playlist_item_activated_handler),
controller );
g_signal_connect( controller->view,
"playlist-item-inserted",
G_CALLBACK(playlist_item_inserted_handler),
controller );
g_signal_connect( controller->view,
"playlist-item-deleted",
G_CALLBACK(playlist_item_deleted_handler),
controller );
g_signal_connect( controller->view,
"playlist-reordered",
G_CALLBACK(playlist_reordered_handler),
controller );
}
static gboolean
update_seek_bar(gpointer data)
{
CelluloidController *controller = data;
gdouble time_pos = celluloid_model_get_time_position(controller->model);
celluloid_view_set_time_position(controller->view, time_pos);
return TRUE;
}
static gboolean
is_more_than_one( GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data )
{
gint64 from = g_value_get_int64(from_value);
g_value_set_boolean(to_value, from > 1);
return TRUE;
}
static void
idle_active_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
CelluloidController *controller = data;
gboolean idle_active = TRUE;
g_object_get(object, "idle-active", &idle_active, NULL);
if(idle_active)
{
celluloid_view_reset(CELLULOID_CONTROLLER(data)->view);
}
else if(controller->target_playlist_pos >= 0)
{
celluloid_model_set_playlist_position
(controller->model, controller->target_playlist_pos);
}
}
static void
playlist_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
GPtrArray *playlist = NULL;
gint64 pos = 0;
g_object_get( object,
"playlist", &playlist,
"playlist-pos", &pos,
NULL );
celluloid_view_update_playlist(view, playlist);
celluloid_view_set_playlist_pos(view, pos);
}
static void
vid_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
CelluloidController *controller = data;
CelluloidMainWindow *window =
celluloid_view_get_main_window(controller->view);
GActionMap *map = G_ACTION_MAP(window);
GAction *action = g_action_map_lookup_action(map, "set-video-size");
gchar *vid_str = NULL;
gint64 vid = 0;
// Queue render to clear last frame from the buffer in case video
// becomes disabled
celluloid_view_queue_render(CELLULOID_CONTROLLER(data)->view);
g_object_get(object, "vid", &vid_str, NULL);
vid = g_ascii_strtoll(vid_str, NULL, 10);
g_simple_action_set_enabled(G_SIMPLE_ACTION(action), vid > 0);
g_free(vid_str);
}
static void
window_scale_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
CelluloidController *controller = data;
gint64 video_width = 1;
gint64 video_height = 1;
gint width = 1;
gint height = 1;
gdouble window_scale = 1.0;
gdouble new_window_scale = 1.0;
celluloid_model_get_video_geometry
(controller->model, &video_width, &video_height);
celluloid_view_get_video_area_geometry
(controller->view, &width, &height);
g_object_get(object, "window-scale", &new_window_scale, NULL);
window_scale = MIN( width/(gdouble)video_width,
height/(gdouble)video_height );
if(window_scale > 0.0 && ABS(window_scale-new_window_scale) > 0.0001)
{
celluloid_controller_autofit(data, new_window_scale);
}
}
static void
model_ready_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
CelluloidController *controller = data;
gboolean ready = FALSE;
g_object_get(object, "ready", &ready, NULL);
if(ready)
{
gboolean use_opengl_cb;
use_opengl_cb = celluloid_model_get_use_opengl_cb
(controller->model);
celluloid_view_set_use_opengl_cb
(controller->view, use_opengl_cb);
if(use_opengl_cb)
{
celluloid_view_make_gl_context_current(controller->view);
celluloid_model_initialize_gl(controller->model);
}
}
g_source_clear(&controller->update_seekbar_id);
controller->update_seekbar_id
= g_timeout_add( SEEK_BAR_UPDATE_INTERVAL,
(GSourceFunc)update_seek_bar,
controller );
controller->ready = ready;
g_object_notify(data, "ready");
}
static void
playlist_replaced_handler(CelluloidModel *model, gpointer data)
{
GSettings *settings = g_settings_new(CONFIG_ROOT);
if(g_settings_get_boolean(settings, "present-window-on-file-open"))
{
celluloid_view_present(CELLULOID_CONTROLLER(data)->view);
}
g_object_unref(settings);
}
static void
frame_ready_handler(CelluloidModel *model, gpointer data)
{
celluloid_view_queue_render(CELLULOID_CONTROLLER(data)->view);
}
static void
metadata_update_handler(CelluloidModel *model, gint64 pos, gpointer data)
{
CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
GPtrArray *playlist = NULL;
g_object_get(G_OBJECT(model), "playlist", &playlist, NULL);
celluloid_view_update_playlist(view, playlist);
}
static void
window_resize_handler( CelluloidModel *model,
gint64 width,
gint64 height,
gpointer data )
{
CelluloidController *controller = data;
celluloid_view_resize_video_area(controller->view, (gint)width, (gint)height);
}
static void
window_move_handler( CelluloidModel *model,
gboolean flip_x,
gboolean flip_y,
GValue *x,
GValue *y,
gpointer data )
{
celluloid_view_move
(CELLULOID_CONTROLLER(data)->view, flip_x, flip_y, x, y);
}
static void
message_handler(CelluloidMpv *mpv, const gchar *message, gpointer data)
{
const gsize prefix_length = sizeof(ACTION_PREFIX)-1;
/* Verify that both the prefix and the scope matches */
if(message && strncmp(message, ACTION_PREFIX, prefix_length) == 0)
{
const gchar *action = message+prefix_length+1;
CelluloidController *controller = data;
CelluloidView *view = controller->view;
GActionMap *map = NULL;
if(g_str_has_prefix(action, "win."))
{
map = G_ACTION_MAP(celluloid_view_get_main_window(view));
}
else if(g_str_has_prefix(action, "app."))
{
map = G_ACTION_MAP(controller->app);
}
if(map)
{
/* Strip scope and activate */
activate_action_string(map, strchr(action, '.')+1);
}
else
{
g_warning( "Received action with missing or "
"unknown scope %s",
action );
}
}
}
static void
error_handler(CelluloidMpv *mpv, const gchar *message, gpointer data)
{
celluloid_view_show_message_dialog
( CELLULOID_CONTROLLER(data)->view,
GTK_MESSAGE_ERROR,
_("Error"),
NULL,
message );
}
static void
shutdown_handler(CelluloidMpv *mpv, gpointer data)
{
g_signal_emit_by_name(data, "shutdown");
}
static gboolean
update_window_scale(gpointer data)
{
gpointer *params = data;
CelluloidController *controller = CELLULOID_CONTROLLER(params[0]);
gint64 video_width = 1;
gint64 video_height = 1;
gint width = 1;
gint height = 1;
gdouble window_scale = 0;
gdouble new_window_scale = 0;
celluloid_model_get_video_geometry
(controller->model, &video_width, &video_height);
celluloid_view_get_video_area_geometry
(controller->view, &width, &height);
new_window_scale = MIN( width/(gdouble)video_width,
height/(gdouble)video_height );
g_object_get(controller->model, "window-scale", &window_scale, NULL);
if(ABS(window_scale-new_window_scale) > 0.0001)
{
g_object_set( controller->model,
"window-scale",
new_window_scale,
NULL );
}
// Clear event source ID for the timeout
*((guint *)params[1]) = 0;
g_free(params);
return FALSE;
}
static void
video_area_resize_handler( CelluloidView *view,
gint width,
gint height,
gpointer data )
{
CelluloidController *controller = data;
gpointer *params = g_new(gpointer, 2);
g_source_clear(&controller->resize_timeout_tag);
params[0] = data;
params[1] = &(controller->resize_timeout_tag);
// Rate-limit the call to update_window_scale(), which will update the
// window-scale property in the model, to no more than once every 250ms.
controller->resize_timeout_tag = g_timeout_add( 250,
update_window_scale,
params );
}
static void
fullscreen_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
CelluloidMainWindow *window = celluloid_view_get_main_window(view);
GActionMap *map = G_ACTION_MAP(window);
GAction *toggle_playlist = NULL;
gboolean fullscreen = FALSE;
toggle_playlist = g_action_map_lookup_action(map, "toggle-playlist");
g_object_get(view, "fullscreen", &fullscreen, NULL);
g_simple_action_set_enabled
(G_SIMPLE_ACTION(toggle_playlist), !fullscreen);
}
static void
searching_handler(GObject *object, GParamSpec *pspec, gpointer data)
{
// When the search box becomes visible, it blocks all keyboard inputs
// from being handled by CelluloidController. This means that the key up
// event for the key that triggered the search will never arrive, so we
// need to explicitly reset key states here.
celluloid_model_reset_keys(CELLULOID_CONTROLLER(data)->model);
}
static void
play_button_handler(GtkButton *button, gpointer data)
{
CelluloidModel *model = CELLULOID_CONTROLLER(data)->model;
gboolean pause = TRUE;
g_object_get(model, "pause", &pause, NULL);
if(pause)
{
celluloid_model_play(model);
}
else
{
celluloid_model_pause(model);
}
}
static void
stop_button_handler(GtkButton *button, gpointer data)
{
celluloid_model_stop(CELLULOID_CONTROLLER(data)->model);
}
static void
forward_button_handler(GtkButton *button, gpointer data)
{
celluloid_model_forward(CELLULOID_CONTROLLER(data)->model);
}
static void
rewind_button_handler(GtkButton *button, gpointer data)
{
celluloid_model_rewind(CELLULOID_CONTROLLER(data)->model);
}
static void
next_button_handler(GtkButton *button, gpointer data)
{
CelluloidController *controller = data;
if(controller->use_skip_buttons_for_playlist)
{
celluloid_model_next_playlist_entry
(CELLULOID_CONTROLLER(data)->model);
}
else
{
celluloid_model_next_chapter
(CELLULOID_CONTROLLER(data)->model);
}
}
static void
previous_button_handler(GtkButton *button, gpointer data)
{
CelluloidController *controller = data;
if(controller->use_skip_buttons_for_playlist)
{
celluloid_model_previous_playlist_entry
(CELLULOID_CONTROLLER(data)->model);
}
else
{
celluloid_model_previous_chapter
(CELLULOID_CONTROLLER(data)->model);
}
}
static void
fullscreen_button_handler(GtkButton *button, gpointer data)
{
CelluloidView *view = CELLULOID_CONTROLLER(data)->view;
gboolean fullscreen = FALSE;
g_object_get(view, "fullscreen", &fullscreen, NULL);
g_object_set(view, "fullscreen", !fullscreen, NULL);
}
static void
seek_handler(GtkButton *button, gdouble value, gpointer data)
{
celluloid_model_seek(CELLULOID_CONTROLLER(data)->model, value);
}
static void
celluloid_controller_class_init(CelluloidControllerClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
GParamSpec *pspec;
obj_class->constructed = constructed;
obj_class->set_property = set_property;
obj_class->get_property = get_property;
obj_class->dispose = dispose;
pspec = g_param_spec_pointer
( "app",
"App",
"The CelluloidApplication to use",
G_PARAM_CONSTRUCT_ONLY|G_PARAM_READWRITE );
g_object_class_install_property(obj_class, PROP_APP, pspec);
pspec = g_param_spec_boolean
( "ready",
"Ready",
"Whether mpv is ready to receive commands",
FALSE,
G_PARAM_READWRITE );
g_object_class_install_property(obj_class, PROP_READY, pspec);
pspec = g_param_spec_boolean
( "idle",
"Idle",
"Whether or not the player is idle",
TRUE,
G_PARAM_READWRITE );
g_object_class_install_property(obj_class, PROP_IDLE, pspec);
pspec = g_param_spec_boolean
( "use-skip-buttons-for-playlist",
"Use skip buttons for playlist",
"Whether or not to use the skip buttons to control the playlist",
FALSE,
G_PARAM_READWRITE );
g_object_class_install_property(obj_class, PROP_USE_SKIP_BUTTONS_FOR_PLAYLIST, pspec);
g_signal_new( "shutdown",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0 );
}
static void
celluloid_controller_init(CelluloidController *controller)
{
controller->key_controller = gtk_event_controller_key_new();
controller->app = NULL;
controller->model = NULL;
controller->view = NULL;
controller->ready = FALSE;
controller->idle = TRUE;
controller->target_playlist_pos = -1;
controller->update_seekbar_id = 0;
controller->resize_timeout_tag = 0;
controller->skip_buttons_binding = NULL;
controller->settings = g_settings_new(CONFIG_ROOT);
controller->media_keys = NULL;
controller->mpris = NULL;
}
static void
update_extra_mpv_options(CelluloidController *controller)
{
GSettings *settings = g_settings_new(CONFIG_ROOT);
const gchar *cli_options = celluloid_application_get_mpv_options
(controller->app);
gchar *pref_options = g_settings_get_string(settings, "mpv-options");
gchar *options = g_strjoin(" ", pref_options, cli_options, NULL);
g_object_set(controller->model, "extra-options", options, NULL);
g_free(options);
g_free(pref_options);
g_object_unref(settings);
}
CelluloidController *
celluloid_controller_new(CelluloidApplication *app)
{
const GType type = celluloid_controller_get_type();
return CELLULOID_CONTROLLER(g_object_new(type, "app", app, NULL));
}
void
celluloid_controller_quit(CelluloidController *controller)
{
celluloid_view_quit(controller->view);
}
void
celluloid_controller_autofit( CelluloidController *controller,
gdouble multiplier )
{
gchar *vid = NULL;
gint64 width = -1;
gint64 height = -1;
g_object_get(G_OBJECT(controller->model), "vid", &vid, NULL);
celluloid_model_get_video_geometry(controller->model, &width, &height);
if(vid && strncmp(vid, "no", 3) != 0 && width >= 0 && width >= 0)
{
gint new_width = (gint)(multiplier*(gdouble)width);
gint new_height = (gint)(multiplier*(gdouble)height);
gint scale = celluloid_view_get_scale_factor(controller->view);
g_debug("Resizing window to %dx%d", new_width, new_height);
celluloid_view_resize_video_area( controller->view,
new_width/scale,
new_height/scale );
}
g_free(vid);
}
void
celluloid_controller_present(CelluloidController *controller)
{
celluloid_view_present(controller->view);
}
void
celluloid_controller_open( CelluloidController *controller,
const gchar *uri,
gboolean append )
{
celluloid_model_load_file(controller->model, uri, append);
}
CelluloidView *
celluloid_controller_get_view(CelluloidController *controller)
{
return controller->view;
}
CelluloidModel *
celluloid_controller_get_model(CelluloidController *controller)
{
return controller->model;
}