/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "clapper-app-application.h" #include "clapper-app-window.h" #include "clapper-app-file-dialog.h" #include "clapper-app-uri-dialog.h" #include "clapper-app-info-window.h" #include "clapper-app-preferences-window.h" #include "clapper-app-about-dialog.h" #include "clapper-app-utils.h" #define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) #define GST_CAT_DEFAULT clapper_app_application_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppApplication { GtkApplication parent; GSettings *settings; GCancellable *cancellable; gboolean need_init_state; }; #define parent_class clapper_app_application_parent_class G_DEFINE_TYPE (ClapperAppApplication, clapper_app_application, GTK_TYPE_APPLICATION); struct ClapperPluginFeatureData { const gchar *name; GstRank rank; }; struct ClapperPluginData { const gchar *name; guint skip_version[3]; struct ClapperPluginFeatureData features[10]; }; typedef struct { ClapperAppApplication *app; guint id; } ClapperAppWindowData; typedef struct { const gchar *action; const gchar *accels[3]; } ClapperAppShortcut; static gboolean clapper_app_options_get (const gchar *key, const gchar *format, GVariantDict *options, GObject *src_object, GSettings *settings, gpointer output) { if (options && g_variant_dict_lookup (options, key, format, output)) { return TRUE; } else if (src_object) { g_object_get (src_object, key, output, NULL); return TRUE; } else if (settings) { g_settings_get (settings, key, format, output); return TRUE; } return FALSE; } static gboolean clapper_app_options_get_extra (const gchar *key, GVariantDict *options, const gchar *extra_value, GSettings *settings, gchar **output) { if (options && g_variant_dict_lookup (options, key, "s", output)) { return TRUE; } else if (extra_value) { *output = g_strdup (extra_value); return TRUE; } else if (settings) { *output = g_settings_get_string (settings, key); /* Ensure non-empty string */ if (*output) { if (strlen (*output) > 0) return TRUE; else g_clear_pointer (output, g_free); } } return FALSE; } /* * Apply options to @dest_window. Option providers will be used in args order. * If any arg is %NULL it will not be used. For example, passing %NULL as * @settings will avoid restoring values to @dest_window from GSettings. */ static void clapper_app_apply_options_to_window (ClapperAppWindow *dest_window, GVariantDict *options, ClapperAppWindow *src_window, GSettings *settings) { ClapperPlayer *dest_player; ClapperAppWindowExtraOptions *src_extra_opts = NULL, *dest_extra_opts = NULL; GObject *src_player_obj = NULL; GObject *src_queue_obj = NULL; gchar *option_str; gdouble option_dbl; gint option_int; gboolean option_bool; GST_DEBUG ("Applying options to window: %p", dest_window); dest_player = clapper_app_window_get_player (dest_window); dest_extra_opts = clapper_app_window_get_extra_options (dest_window); if (src_window) { src_player_obj = (GObject *) clapper_app_window_get_player (src_window); src_queue_obj = (GObject *) clapper_player_get_queue (CLAPPER_PLAYER_CAST (src_player_obj)); src_extra_opts = clapper_app_window_get_extra_options (src_window); } /* Apply player values, clamp them to be within allowed range */ if (clapper_app_options_get ("volume", "d", options, src_player_obj, settings, &option_dbl)) clapper_player_set_volume (dest_player, PERCENTAGE_ROUND (CLAMP (option_dbl, 0, 2.0))); if (clapper_app_options_get ("mute", "b", NULL, src_player_obj, settings, &option_bool)) clapper_player_set_mute (dest_player, option_bool); if (clapper_app_options_get ("speed", "d", options, src_player_obj, settings, &option_dbl)) clapper_player_set_speed (dest_player, PERCENTAGE_ROUND (CLAMP (option_dbl, 0.05, 2.0))); if (clapper_app_options_get ("adaptive-start-bitrate", "i", options, src_player_obj, settings, &option_int)) clapper_player_set_adaptive_start_bitrate (dest_player, option_int); if (clapper_app_options_get ("progression-mode", "i", options, src_queue_obj, settings, &option_int)) clapper_queue_set_progression_mode (clapper_player_get_queue (dest_player), CLAMP (option_int, 0, 4)); if (clapper_app_options_get ("subtitles-enabled", "b", NULL, src_player_obj, settings, &option_bool)) clapper_player_set_subtitles_enabled (dest_player, option_bool); if (clapper_app_options_get_extra ("video-filter", options, (src_extra_opts) ? src_extra_opts->video_filter : NULL, NULL, &option_str)) { clapper_player_set_video_filter (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->video_filter); dest_extra_opts->video_filter = option_str; } if (clapper_app_options_get_extra ("audio-filter", options, (src_extra_opts) ? src_extra_opts->audio_filter : NULL, NULL, &option_str)) { clapper_player_set_audio_filter (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->audio_filter); dest_extra_opts->audio_filter = option_str; } if (clapper_app_options_get_extra ("video-sink", options, (src_extra_opts) ? src_extra_opts->video_sink : NULL, NULL, &option_str)) { clapper_player_set_video_sink (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->video_sink); dest_extra_opts->video_sink = option_str; } if (clapper_app_options_get_extra ("audio-sink", options, (src_extra_opts) ? src_extra_opts->audio_sink : NULL, NULL, &option_str)) { clapper_player_set_audio_sink (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->audio_sink); dest_extra_opts->audio_sink = option_str; } /* Apply window options */ if ((options && g_variant_dict_contains (options, "fullscreen")) || (settings && g_settings_get_boolean (settings, "fullscreened"))) gtk_window_fullscreen (GTK_WINDOW (dest_window)); else if (settings && g_settings_get_boolean (settings, "maximized")) gtk_window_maximize (GTK_WINDOW (dest_window)); GST_DEBUG ("Options applied"); } static inline void _store_settings_from_window (ClapperAppApplication *self, ClapperAppWindow *app_window) { ClapperPlayer *player = clapper_app_window_get_player (app_window); ClapperQueue *queue = clapper_player_get_queue (player); GtkWindow *window = GTK_WINDOW (app_window); GST_DEBUG ("Storing current configuration to GSettings"); g_settings_set_double (self->settings, "volume", clapper_player_get_volume (player)); g_settings_set_boolean (self->settings, "mute", clapper_player_get_mute (player)); g_settings_set_double (self->settings, "speed", clapper_player_get_speed (player)); g_settings_set_int (self->settings, "adaptive-start-bitrate", CLAMP (clapper_player_get_adaptive_bandwidth (player) * 0.8, 0, G_MAXINT)); g_settings_set_boolean (self->settings, "subtitles-enabled", clapper_player_get_subtitles_enabled (player)); g_settings_set_int (self->settings, "progression-mode", clapper_queue_get_progression_mode (queue)); g_settings_set_boolean (self->settings, "maximized", gtk_window_is_maximized (window)); g_settings_set_boolean (self->settings, "fullscreened", gtk_window_is_fullscreen (window)); GST_DEBUG ("Configuration stored"); } static inline void _set_initial_plugin_feature_ranks (void) { GstRegistry *registry = gst_registry_get (); guint i; const struct ClapperPluginData plugins_data[] = { { .name = "va", .skip_version = { 1, 24, 0 }, .features = { { "vah264dec", GST_RANK_PRIMARY + 24 }, { "vah265dec", GST_RANK_PRIMARY + 24 }, { "vavp8dec", GST_RANK_PRIMARY + 24 }, { "vavp9dec", GST_RANK_PRIMARY + 24 }, { "vaav1dec", GST_RANK_PRIMARY + 24 }, { NULL, 0 } } }, { .name = "nvcodec", .skip_version = { 1, 24, 0 }, .features = { { "nvh264dec", GST_RANK_PRIMARY + 28 }, { "nvh265dec", GST_RANK_PRIMARY + 28 }, { "nvvp8dec", GST_RANK_PRIMARY + 28 }, { "nvvp9dec", GST_RANK_PRIMARY + 28 }, { "nvav1dec", GST_RANK_PRIMARY + 28 }, { NULL, 0 } } } }; for (i = 0; i < G_N_ELEMENTS (plugins_data); ++i) { GList *features; if (!(features = gst_registry_get_feature_list_by_plugin ( registry, plugins_data[i].name))) continue; if (g_list_length (features) > 0) { guint j; for (j = 0; G_N_ELEMENTS (plugins_data[i].features); ++j) { GstPluginFeature *feature; if (!plugins_data[i].features[j].name) break; if (!(feature = gst_registry_lookup_feature (registry, plugins_data[i].features[j].name))) continue; if (!gst_plugin_feature_check_version (feature, plugins_data[i].skip_version[0], plugins_data[i].skip_version[1], plugins_data[i].skip_version[2])) { gst_plugin_feature_set_rank (feature, plugins_data[i].features[j].rank); GST_DEBUG ("Initially set \"%s\" rank to: %i", plugins_data[i].features[j].name, plugins_data[i].features[j].rank); } gst_object_unref (feature); } } gst_plugin_feature_list_free (features); } } static void _iter_ranks_func (const gchar *feature_name, GstRank rank, gboolean from_env, gpointer user_data G_GNUC_UNUSED) { GstPluginFeature *feature; if ((feature = gst_registry_find_feature (gst_registry_get (), feature_name, GST_TYPE_ELEMENT_FACTORY))) { gst_plugin_feature_set_rank (feature, rank); GST_INFO ("Set \"%s\" rank to: %i", feature_name, rank); gst_object_unref (feature); } } static void plugin_feature_ranks_settings_changed_cb (GSettings *settings, gchar *key G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { clapper_app_utils_iterate_plugin_feature_ranks (settings, (ClapperAppUtilsIterRanks) _iter_ranks_func, NULL); } static void _assemble_initial_state (GtkWindow *window) { GtkWidget *stack = gtk_window_get_child (window); GtkBuilder *builder = gtk_builder_new_from_resource ( CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-initial-state.ui"); GtkWidget *initial_state = GTK_WIDGET (gtk_builder_get_object (builder, "initial_state")); gtk_stack_add_named (GTK_STACK (stack), initial_state, "initial_state"); gtk_stack_set_visible_child (GTK_STACK (stack), initial_state); g_object_unref (builder); } static void _show_error_dialog (GError *error, GtkWindow *parent) { AdwDialog *dialog; dialog = adw_alert_dialog_new ("Error", error->message); adw_alert_dialog_add_response (ADW_ALERT_DIALOG (dialog), "close", _("Close")); adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "close"); adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "close"); adw_dialog_present (dialog, GTK_WIDGET (parent)); } static void add_files (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); clapper_app_file_dialog_open_files (gtk_app); } static void add_uri (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); clapper_app_uri_dialog_open_uri (gtk_app); } static void clear_queue (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWindow *window = gtk_application_get_active_window (gtk_app); ClapperPlayer *player; ClapperQueue *queue; while (window && !CLAPPER_APP_IS_WINDOW (window)) window = gtk_window_get_transient_for (window); player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)); queue = clapper_player_get_queue (player); clapper_queue_clear (queue); } static void new_window (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *stack = gtk_window_get_child (gtk_application_get_active_window (gtk_app)); const gchar *child_name = gtk_stack_get_visible_child_name (GTK_STACK (stack)); /* Do not allow to open new windows during initial state, * there already is a free one to use */ if (g_strcmp0 (child_name, "initial_state") != 0) { ClapperAppWindow *src_window, *dest_window; src_window = CLAPPER_APP_WINDOW_CAST (gtk_application_get_active_window (gtk_app)); dest_window = CLAPPER_APP_WINDOW_CAST (clapper_app_window_new (gtk_app)); clapper_app_apply_options_to_window (dest_window, NULL, src_window, NULL); gtk_window_present (GTK_WINDOW (dest_window)); } } static void show_preferences (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *preferences_window; preferences_window = clapper_app_preferences_window_new (gtk_app); gtk_window_present (GTK_WINDOW (preferences_window)); } static void show_info (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *info_window; GtkWindow *window; ClapperPlayer *player; window = gtk_application_get_active_window (gtk_app); player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)); info_window = clapper_app_info_window_new (gtk_app, player); gtk_window_present (GTK_WINDOW (info_window)); } static void _launch_pipeline_cb (GtkFileLauncher *launcher, GAsyncResult *res, ClapperAppWindowData *win_data) { GError *error = NULL; if (!gtk_file_launcher_launch_finish (launcher, res, &error)) { if (error->domain != GTK_DIALOG_ERROR || error->code != GTK_DIALOG_ERROR_DISMISSED) { GtkWindow *window; GST_ERROR ("Could not launch pipeline preview, reason: %s", GST_STR_NULL (error->message)); if ((window = gtk_application_get_window_by_id ( GTK_APPLICATION (win_data->app), win_data->id))) _show_error_dialog (error, window); } g_error_free (error); } g_free (win_data); } static void _show_pipeline_cb (GObject *source G_GNUC_UNUSED, GAsyncResult *res, ClapperAppWindowData *win_data) { GTask *task = G_TASK (res); GtkWindow *window; GFile *svg_file; GError *error = NULL; svg_file = (GFile *) g_task_propagate_pointer (task, &error); window = gtk_application_get_window_by_id ( GTK_APPLICATION (win_data->app), win_data->id); if (error) { if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED) { GST_ERROR ("Could not create pipeline graph file, reason: %s", GST_STR_NULL (error->message)); if (window) _show_error_dialog (error, window); } g_error_free (error); g_free (win_data); return; } if (window) { GtkFileLauncher *launcher = gtk_file_launcher_new (svg_file); #if GTK_CHECK_VERSION(4,12,0) gtk_file_launcher_set_always_ask (launcher, TRUE); #endif gtk_file_launcher_launch (launcher, window, NULL, (GAsyncReadyCallback) _launch_pipeline_cb, win_data); g_object_unref (launcher); } else { g_free (win_data); } g_object_unref (svg_file); } static void show_pipeline (GSimpleAction *action, GVariant *param, gpointer user_data) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (user_data); GtkApplication *gtk_app = GTK_APPLICATION (self); GtkWindow *window; ClapperAppWindowData *win_data; window = gtk_application_get_active_window (gtk_app); while (window && !CLAPPER_APP_IS_WINDOW (window)) window = gtk_window_get_transient_for (window); if (G_UNLIKELY (window == NULL)) return; if (self->cancellable) { g_cancellable_cancel (self->cancellable); g_object_unref (self->cancellable); } self->cancellable = g_cancellable_new (); win_data = g_new (ClapperAppWindowData, 1); win_data->app = self; win_data->id = gtk_application_window_get_id (GTK_APPLICATION_WINDOW (window)); clapper_app_utils_create_pipeline_svg_file_async ( clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)), self->cancellable, (GAsyncReadyCallback) _show_pipeline_cb, win_data); } static void show_about (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWindow *window; GtkWidget *about_dialog; window = gtk_application_get_active_window (gtk_app); about_dialog = clapper_app_about_dialog_new (); adw_dialog_present (ADW_DIALOG (about_dialog), GTK_WIDGET (window)); } GApplication * clapper_app_application_new (void) { return g_object_new (CLAPPER_APP_TYPE_APPLICATION, "application-id", CLAPPER_APP_ID, "flags", G_APPLICATION_HANDLES_OPEN | G_APPLICATION_HANDLES_COMMAND_LINE, NULL); } static void clapper_app_application_window_removed (GtkApplication *gtk_app, GtkWindow *window) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (gtk_app); if (CLAPPER_APP_IS_WINDOW (window)) { GList *win, *windows = gtk_application_get_windows (gtk_app); gboolean has_player_windows = FALSE; for (win = windows; win != NULL; win = win->next) { GtkWindow *rem_window = GTK_WINDOW (win->data); if ((has_player_windows = (rem_window != window && CLAPPER_APP_IS_WINDOW (rem_window)))) break; } /* Last player window is closing, time to store settings */ if (!has_player_windows) _store_settings_from_window (self, CLAPPER_APP_WINDOW_CAST (window)); } GTK_APPLICATION_CLASS (parent_class)->window_removed (gtk_app, window); } static void clapper_app_application_activate (GApplication *app) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (app); GtkApplication *gtk_app = GTK_APPLICATION (app); GtkWindow *window; GST_INFO ("Activate"); G_APPLICATION_CLASS (parent_class)->activate (app); /* When activated through DBus command line does not run, * so create our first window here instead */ if (!(window = gtk_application_get_active_window (gtk_app))) { window = GTK_WINDOW (clapper_app_window_new (gtk_app)); clapper_app_apply_options_to_window (CLAPPER_APP_WINDOW_CAST (window), NULL, NULL, self->settings); } if (self->need_init_state) { _assemble_initial_state (window); self->need_init_state = FALSE; } gtk_window_present (window); } static gboolean clapper_app_application_local_command_line (GApplication *app, gchar ***arguments, gint *exit_status) { gchar **argv = *arguments; guint i; GST_INFO ("Handling local command line"); /* NOTE: argv is never NULL, so no need to check */ for (i = 0; argv[i]; ++i) { /* Handle "-" special case as URI */ if (strlen (argv[i]) == 1 && argv[i][0] == '-') { g_free (argv[i]); argv[i] = g_strdup ("fd://0"); } } return G_APPLICATION_CLASS (parent_class)->local_command_line (app, arguments, exit_status); } static gint clapper_app_application_command_line (GApplication *app, GApplicationCommandLine *cmd_line) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (app); ClapperAppWindow *src_window = NULL, *dest_window = NULL; GtkApplication *gtk_app = GTK_APPLICATION (app); GVariantDict *options; GFile **files = NULL; GSettings *settings; gint n_files = 0; GST_INFO ("Handling command line"); options = g_application_command_line_get_options_dict (cmd_line); dest_window = CLAPPER_APP_WINDOW_CAST (gtk_application_get_active_window (gtk_app)); /* Restore settings only once by making them %NULL when run again */ settings = (!dest_window) ? self->settings : NULL; if (!dest_window || g_variant_dict_contains (options, "new-window")) { src_window = dest_window; dest_window = CLAPPER_APP_WINDOW_CAST (clapper_app_window_new (gtk_app)); } clapper_app_apply_options_to_window (dest_window, options, src_window, settings); if (clapper_app_utils_files_from_command_line (cmd_line, &files, &n_files)) { g_application_open (app, files, n_files, (g_variant_dict_contains (options, "enqueue")) ? "add-only" : ""); clapper_app_utils_files_free (files); } else { g_application_activate (app); } return EXIT_SUCCESS; } static void add_item_from_file (GFile *file, ClapperQueue *queue) { ClapperMediaItem *item = clapper_media_item_new_from_file (file); GST_DEBUG ("Adding media item with URI: %s", clapper_media_item_get_uri (item)); clapper_queue_add_item (queue, item); gst_object_unref (item); } static void add_item_with_subtitles (GFile *media_file, GFile *subs_file, ClapperQueue *queue) { ClapperMediaItem *item = clapper_media_item_new_from_file (media_file); gchar *suburi = g_file_get_uri (subs_file); GST_DEBUG ("Adding media item with URI: %s, SUBURI: %s", clapper_media_item_get_uri (item), GST_STR_NULL (suburi)); clapper_media_item_set_suburi (item, suburi); clapper_queue_add_item (queue, item); gst_object_unref (item); g_free (suburi); } static void clapper_app_application_open (GApplication *app, GFile **files, gint n_files, const gchar *hint) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (app); GtkWindow *window; ClapperPlayer *player; ClapperQueue *queue; guint n_before; gboolean add_only, handled = FALSE; GST_INFO ("Open"); /* Since we startup with media, * no need to show initial state */ self->need_init_state = FALSE; g_application_activate (app); g_application_mark_busy (app); window = gtk_application_get_active_window (GTK_APPLICATION (app)); while (window && !CLAPPER_APP_IS_WINDOW (window)) window = gtk_window_get_transient_for (window); clapper_app_window_ensure_no_initial_state (CLAPPER_APP_WINDOW (window)); player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)); queue = clapper_player_get_queue (player); n_before = clapper_queue_get_n_items (queue); /* Special path for opening video with subtitles at once */ if (n_files == 2) { gboolean first_subs, second_subs; first_subs = clapper_app_utils_is_subtitles_file (files[0]); second_subs = clapper_app_utils_is_subtitles_file (files[1]); if ((handled = first_subs != second_subs)) { guint media_index, subs_index; media_index = (second_subs) ? 0 : 1; subs_index = (media_index + 1) % 2; add_item_with_subtitles ( files[media_index], files[subs_index], queue); } } if (!handled) { gint i; for (i = 0; i < n_files; ++i) add_item_from_file (files[i], queue); } add_only = (g_strcmp0 (hint, "add-only") == 0); /* Select first thing from added item to play (behave like "open" should), * when queue was empty first item is automatically selected */ if (!add_only && n_before > 0) clapper_queue_select_index (queue, n_before); g_application_unmark_busy (app); } static void clapper_app_application_init (ClapperAppApplication *self) { self->need_init_state = TRUE; } static void clapper_app_application_constructed (GObject *object) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (object); GApplication *app = G_APPLICATION (self); guint i; const GOptionEntry app_options[] = { { "new-window", 'n', 0, G_OPTION_ARG_NONE, NULL, _("Create a new window"), NULL }, { "enqueue", 0, 0, G_OPTION_ARG_NONE, NULL, _("Add media to queue in primary application instance"), NULL }, { "volume", 0, 0, G_OPTION_ARG_DOUBLE, NULL, _("Audio volume to set (0 - 2.0 range)"), NULL }, { "speed", 0, 0, G_OPTION_ARG_DOUBLE, NULL, _("Playback speed to set (0.05 - 2.0 range)"), NULL }, { "adaptive-start-bitrate", 0, 0, G_OPTION_ARG_INT, NULL, _("Initial bitrate for adaptive streaming"), NULL }, { "progression-mode", 0, 0, G_OPTION_ARG_INT, NULL, _("Initial queue progression mode (0=none, 1=consecutive, 2=repeat-item, 3=carousel, 4=shuffle)"), NULL }, { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, NULL, _("Set window to be fullscreen"), NULL }, { "video-filter", 0, 0, G_OPTION_ARG_STRING, NULL, _("Video filter to use (\"none\" to disable)"), NULL }, { "audio-filter", 0, 0, G_OPTION_ARG_STRING, NULL, _("Audio filter to use (\"none\" to disable)"), NULL }, { "video-sink", 0, 0, G_OPTION_ARG_STRING, NULL, _("Video sink to use"), NULL }, { "audio-sink", 0, 0, G_OPTION_ARG_STRING, NULL, _("Audio sink to use"), NULL }, { NULL } }; static const GActionEntry app_actions[] = { { "add-files", add_files, NULL, NULL, NULL }, { "add-uri", add_uri, NULL, NULL, NULL }, { "clear-queue", clear_queue, NULL, NULL, NULL }, { "new-window", new_window, NULL, NULL, NULL }, { "info", show_info, NULL, NULL, NULL }, { "pipeline", show_pipeline, NULL, NULL, NULL }, { "preferences", show_preferences, NULL, NULL, NULL }, { "about", show_about, NULL, NULL, NULL }, }; static const ClapperAppShortcut app_shortcuts[] = { { "app.add-files", { "o", NULL, NULL }}, { "app.add-uri", { "u", NULL, NULL }}, { "app.new-window", { "n", NULL, NULL }}, { "app.info", { "i", NULL, NULL }}, { "app.pipeline", { "p", NULL, NULL }}, { "app.preferences", { "comma", NULL, NULL }}, { "app.about", { "F1", NULL, NULL }}, { "win.toggle-fullscreen", { "F11", "f", NULL }}, { "win.unfullscreen", { "Escape", NULL, NULL }}, { "win.auto-resize", { "r", NULL, NULL }}, { "win.show-help-overlay", { "question", NULL, NULL }}, { "window.close", { "q", "q", NULL }}, }; /* Override initial ranks, they will be updated * from both stored settings and env below */ _set_initial_plugin_feature_ranks (); self->settings = g_settings_new (CLAPPER_APP_ID); g_signal_connect (self->settings, "changed::plugin-feature-ranks", G_CALLBACK (plugin_feature_ranks_settings_changed_cb), self); plugin_feature_ranks_settings_changed_cb (self->settings, NULL, NULL); g_action_map_add_action_entries (G_ACTION_MAP (app), app_actions, G_N_ELEMENTS (app_actions), app); for (i = 0; i < G_N_ELEMENTS (app_shortcuts); ++i) gtk_application_set_accels_for_action (GTK_APPLICATION (app), app_shortcuts[i].action, app_shortcuts[i].accels); g_application_set_option_context_parameter_string (app, "[URI1|FILE1] [URI2|FILE2] …"); g_application_add_main_option_entries (app, app_options); g_application_add_option_group (app, gst_init_get_option_group ()); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_app_application_dispose (GObject *object) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (object); if (self->cancellable) { g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); } G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_application_finalize (GObject *object) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (object); GST_TRACE ("Finalize"); g_object_unref (self->settings); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_application_class_init (ClapperAppApplicationClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GApplicationClass *application_class = (GApplicationClass *) klass; GtkApplicationClass *gtk_application_class = (GtkApplicationClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappapplication", 0, "Clapper App Application"); gobject_class->constructed = clapper_app_application_constructed; gobject_class->dispose = clapper_app_application_dispose; gobject_class->finalize = clapper_app_application_finalize; gtk_application_class->window_removed = clapper_app_application_window_removed; application_class->activate = clapper_app_application_activate; application_class->local_command_line = clapper_app_application_local_command_line; application_class->command_line = clapper_app_application_command_line; application_class->open = clapper_app_application_open; }