diff --git a/src/bin/clapper-app/clapper-app-file-dialog.c b/src/bin/clapper-app/clapper-app-file-dialog.c index e8da0178..722cb751 100644 --- a/src/bin/clapper-app/clapper-app-file-dialog.c +++ b/src/bin/clapper-app/clapper-app-file-dialog.c @@ -72,6 +72,43 @@ _open_subtitles_cb (GtkFileDialog *dialog, GAsyncResult *result, ClapperMediaIte gst_object_unref (item); // Borrowed reference } +static void +_on_select_file_dir_finish (GFile *file, AdwActionRow *action_row, GError *error) +{ + if (G_LIKELY (error == NULL)) { + gchar *path = g_file_get_path (file); + + adw_action_row_set_subtitle (action_row, path); + g_free (path); + } else { + if (error->domain != GTK_DIALOG_ERROR || error->code != GTK_DIALOG_ERROR_DISMISSED) { + g_printerr ("Error: %s\n", + (error->message) ? error->message : "Could not open file dialog"); + } + g_error_free (error); + } + g_clear_object (&file); + g_object_unref (action_row); // Borrowed reference +} + +static void +_select_file_cb (GtkFileDialog *dialog, GAsyncResult *result, AdwActionRow *action_row) +{ + GError *error = NULL; + GFile *file = gtk_file_dialog_open_finish (dialog, result, &error); + + _on_select_file_dir_finish (file, action_row, error); +} + +static void +_select_dir_cb (GtkFileDialog *dialog, GAsyncResult *result, AdwActionRow *action_row) +{ + GError *error = NULL; + GFile *file = gtk_file_dialog_select_folder_finish (dialog, result, &error); + + _on_select_file_dir_finish (file, action_row, error); +} + static void _dialog_add_mime_types (GtkFileDialog *dialog, const gchar *filter_name, const gchar *const *mime_types) @@ -144,3 +181,35 @@ clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaIte g_object_unref (dialog); } + +void +clapper_app_file_dialog_select_prefs_file (GtkApplication *gtk_app, AdwActionRow *action_row) +{ + GtkWindow *window = gtk_application_get_active_window (gtk_app); + GtkFileDialog *dialog = gtk_file_dialog_new (); + + gtk_file_dialog_set_modal (dialog, TRUE); + gtk_file_dialog_set_title (dialog, "Select File"); + + gtk_file_dialog_open (dialog, window, NULL, + (GAsyncReadyCallback) _select_file_cb, + g_object_ref (action_row)); + + g_object_unref (dialog); +} + +void +clapper_app_file_dialog_select_prefs_dir (GtkApplication *gtk_app, AdwActionRow *action_row) +{ + GtkWindow *window = gtk_application_get_active_window (gtk_app); + GtkFileDialog *dialog = gtk_file_dialog_new (); + + gtk_file_dialog_set_modal (dialog, TRUE); + gtk_file_dialog_set_title (dialog, "Select Folder"); + + gtk_file_dialog_select_folder (dialog, window, NULL, + (GAsyncReadyCallback) _select_dir_cb, + g_object_ref (action_row)); + + g_object_unref (dialog); +} diff --git a/src/bin/clapper-app/clapper-app-file-dialog.h b/src/bin/clapper-app/clapper-app-file-dialog.h index 068982c6..55befcb6 100644 --- a/src/bin/clapper-app/clapper-app-file-dialog.h +++ b/src/bin/clapper-app/clapper-app-file-dialog.h @@ -19,6 +19,7 @@ #include #include +#include #include G_BEGIN_DECLS @@ -29,4 +30,10 @@ void clapper_app_file_dialog_open_files (GtkApplication *gtk_app); G_GNUC_INTERNAL void clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaItem *item); +G_GNUC_INTERNAL +void clapper_app_file_dialog_select_prefs_file (GtkApplication *gtk_app, AdwActionRow *action_row); + +G_GNUC_INTERNAL +void clapper_app_file_dialog_select_prefs_dir (GtkApplication *gtk_app, AdwActionRow *action_row); + G_END_DECLS diff --git a/src/bin/clapper-app/clapper-app-preferences-window.c b/src/bin/clapper-app/clapper-app-preferences-window.c index 8a707dcc..fa51eaba 100644 --- a/src/bin/clapper-app/clapper-app-preferences-window.c +++ b/src/bin/clapper-app/clapper-app-preferences-window.c @@ -23,6 +23,7 @@ #include "clapper-app-preferences-window.h" #include "clapper-app-application.h" +#include "clapper-app-file-dialog.h" #include "clapper-app-utils.h" #define GST_CAT_DEFAULT clapper_app_preferences_window_debug @@ -41,6 +42,14 @@ struct _ClapperAppPreferencesWindow AdwSpinRow *subtitle_offset_spin_row; GtkFontDialogButton *font_dialog_button; + GtkStack *enhancers_stack; + GtkWidget *browse_enhancers_page; + GtkWidget *no_enhancers_page; + + AdwNavigationPage *enhancers_subpage; + AdwComboRow *enhancers_combo_row; + AdwPreferencesGroup *enhancer_config_group; + AdwNavigationPage *plugins_subpage; AdwComboRow *plugins_combo_row; AdwComboRow *features_combo_row; @@ -48,6 +57,8 @@ struct _ClapperAppPreferencesWindow GSettings *settings; + GList *enhancer_pspec_rows; + GList *features; GtkStringList *plugins_list; @@ -67,6 +78,19 @@ typedef struct gboolean updated; } ClapperAppPreferencesIterRanksData; +typedef struct +{ + GSettings *settings; + const gchar *key; +} ClapperAppPreferencesResetData; + +typedef struct +{ + GSettings *settings; + GParamSpec *pspec; + guint flag; +} ClapperAppPreferencesFlagMapData; + enum { PROP_0, @@ -76,6 +100,347 @@ enum static GParamSpec *param_specs[PROP_LAST] = { NULL, }; +static void +_flag_map_data_free (ClapperAppPreferencesFlagMapData *data) +{ + GST_TRACE ("Destroying flag map data: %p", data); + g_object_unref (data->settings); + g_free (data); +} + +static void +_reset_button_closure (ClapperAppPreferencesResetData *data, GClosure *closure) +{ + GST_TRACE ("Destroying reset button data: %p", data); + g_object_unref (data->settings); + g_free (data); +} + +static void +_reset_button_clicked_cb (GtkButton *button, ClapperAppPreferencesResetData *data) +{ + g_settings_reset (data->settings, data->key); +} + +static void +file_selection_row_activated_cb (AdwActionRow *action_row, GParamSpec *pspec) +{ + GtkApplication *gtk_app; + GtkWidget *window; + + if (!(window = gtk_widget_get_ancestor (GTK_WIDGET (action_row), GTK_TYPE_WINDOW))) { + GST_ERROR ("Could not get a hold of parent window"); + return; + } + + gtk_app = gtk_window_get_application (GTK_WINDOW (window)); + + if (pspec->flags & CLAPPER_ENHANCER_PARAM_FILEPATH) + clapper_app_file_dialog_select_prefs_file (gtk_app, action_row); + else + clapper_app_file_dialog_select_prefs_dir (gtk_app, action_row); +} + +static gboolean +_get_enum_mapping (GValue *value, GVariant *variant, GParamSpec *pspec) +{ + GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (pspec->value_type)); + const gchar *selected_str = g_variant_get_string (variant, NULL); + guint i, selected = 0; + + for (i = 0; i < enum_class->n_values; ++i) { + if (g_strcmp0 (selected_str, enum_class->values[i].value_nick) == 0) { + selected = i; + break; + } + } + + g_value_set_uint (value, selected); + return TRUE; +} + +static GVariant * +_set_enum_mapping (GValue *value, GVariantType *exp_type, GParamSpec *pspec) +{ + GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (pspec->value_type)); + guint selected = g_value_get_uint (value); + + if (G_UNLIKELY (selected == GTK_INVALID_LIST_POSITION)) + selected = 0; + + return g_variant_new_string (enum_class->values[selected].value_nick); +} + +static gboolean +_get_flag_mapping (GValue *value, GVariant *variant, ClapperAppPreferencesFlagMapData *data) +{ + guint flags = g_settings_get_flags (data->settings, data->pspec->name); + + g_value_set_boolean (value, (flags & data->flag)); + return TRUE; +} + +static GVariant * +_set_flag_mapping (GValue *value, GVariantType *exp_type, ClapperAppPreferencesFlagMapData *data) +{ + GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (data->pspec->value_type)); + GStrvBuilder *builder; + GVariant *variant; + gchar **strv; + gboolean active = g_value_get_boolean (value); + guint i, flags = g_settings_get_flags (data->settings, data->pspec->name); + + if (active) + flags |= data->flag; + else + flags &= ~(data->flag); + + builder = g_strv_builder_new (); + + for (i = 0; i < flags_class->n_values; ++i) { + if (flags & flags_class->values[i].value) + g_strv_builder_add (builder, flags_class->values[i].value_nick); + } + + strv = g_strv_builder_end (builder); + g_strv_builder_unref (builder); + + variant = g_variant_new_strv ((const gchar *const *) strv, -1); + g_strfreev (strv); + + return variant; +} + +static gboolean +_add_enhancer_config_row (ClapperAppPreferencesWindow *self, GParamSpec *pspec, + GSettings *enhancer_settings) +{ + GtkWidget *row = NULL, *reset_button; + ClapperAppPreferencesResetData *reset_data; + const gchar *bind_prop = NULL; + gboolean is_enum = FALSE, is_flags = FALSE; + + switch (pspec->value_type) { + case G_TYPE_BOOLEAN:{ + row = adw_switch_row_new (); + break; + } + case G_TYPE_INT:{ + GParamSpecInt *p = (GParamSpecInt *) pspec; + row = adw_spin_row_new_with_range (p->minimum, p->maximum, 1); + break; + } + case G_TYPE_UINT:{ + GParamSpecUInt *p = (GParamSpecUInt *) pspec; + row = adw_spin_row_new_with_range (p->minimum, p->maximum, 1); + break; + } + case G_TYPE_DOUBLE:{ + GParamSpecDouble *p = (GParamSpecDouble *) pspec; + row = adw_spin_row_new_with_range (p->minimum, p->maximum, 0.25); + break; + } + case G_TYPE_STRING:{ + if (pspec->flags & (CLAPPER_ENHANCER_PARAM_FILEPATH | CLAPPER_ENHANCER_PARAM_DIRPATH)) { + GtkWidget *image; + + image = gtk_image_new_from_icon_name ("document-open-symbolic"); + gtk_widget_set_margin_end (image, 10); // matches other rows + + row = adw_action_row_new (); + adw_action_row_add_suffix (ADW_ACTION_ROW (row), image); + adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), image); + + g_signal_connect (row, "activated", + G_CALLBACK (file_selection_row_activated_cb), pspec); + } else { + row = adw_entry_row_new (); + } + break; + } + default:{ + if ((is_enum = G_IS_PARAM_SPEC_ENUM (pspec))) { + GtkExpression *expression; + AdwEnumListModel *enum_model; + + row = adw_combo_row_new (); + expression = gtk_property_expression_new (ADW_TYPE_ENUM_LIST_ITEM, NULL, "nick"); + adw_combo_row_set_expression (ADW_COMBO_ROW (row), expression); + + enum_model = adw_enum_list_model_new (pspec->value_type); + adw_combo_row_set_model (ADW_COMBO_ROW (row), G_LIST_MODEL (enum_model)); + + gtk_expression_unref (expression); + g_object_unref (enum_model); + break; + } else if ((is_flags = G_IS_PARAM_SPEC_FLAGS (pspec))) { + GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (pspec->value_type)); + guint i; + + row = adw_expander_row_new (); + + for (i = 0; i < flags_class->n_values; ++i) { + GtkWidget *flag_row = adw_switch_row_new (); + ClapperAppPreferencesFlagMapData *fm_data; + + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (flag_row), + flags_class->values[i].value_nick); + + fm_data = g_new (ClapperAppPreferencesFlagMapData, 1); + fm_data->settings = g_object_ref (enhancer_settings); + fm_data->pspec = pspec; + fm_data->flag = flags_class->values[i].value; + + GST_TRACE ("Created flag map data: %p", fm_data); + + g_settings_bind_with_mapping (enhancer_settings, pspec->name, flag_row, + "active", G_SETTINGS_BIND_DEFAULT, + (GSettingsBindGetMapping) _get_flag_mapping, + (GSettingsBindSetMapping) _set_flag_mapping, + fm_data, (GDestroyNotify) _flag_map_data_free); + + adw_expander_row_add_row (ADW_EXPANDER_ROW (row), flag_row); + } + break; + } + g_warning ("Unsupported enhancer \"%s\" property type: %s", + pspec->name, g_type_name (pspec->value_type)); + return FALSE; + } + } + + reset_button = gtk_button_new_from_icon_name ("view-refresh-symbolic"); + gtk_widget_set_tooltip_text (reset_button, _("Restore default")); + + gtk_widget_set_halign (reset_button, GTK_ALIGN_CENTER); + gtk_widget_set_valign (reset_button, GTK_ALIGN_CENTER); + gtk_widget_add_css_class (reset_button, "circular"); + + gtk_widget_set_tooltip_text (row, g_param_spec_get_blurb (pspec)); + + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), g_param_spec_get_nick (pspec)); + + if (ADW_IS_SWITCH_ROW (row)) { + bind_prop = "active"; + } else if (ADW_IS_SPIN_ROW (row)) { + bind_prop = "value"; + adw_spin_row_set_numeric (ADW_SPIN_ROW (row), TRUE); + } else if (ADW_IS_ENTRY_ROW (row)) { + bind_prop = "text"; + } else if (ADW_IS_COMBO_ROW (row)) { + bind_prop = "selected"; + } else if (ADW_IS_ACTION_ROW (row)) { + bind_prop = "subtitle"; + } else if (!is_flags) { // In case of flags we bind individual widgets + g_assert_not_reached (); + return FALSE; + } + + if (ADW_IS_ENTRY_ROW (row)) + adw_entry_row_add_prefix (ADW_ENTRY_ROW (row), reset_button); + else if (ADW_IS_ACTION_ROW (row)) + adw_action_row_add_prefix (ADW_ACTION_ROW (row), reset_button); + else if (ADW_IS_EXPANDER_ROW (row)) + adw_expander_row_add_prefix (ADW_EXPANDER_ROW (row), reset_button); + + if (is_enum) { + g_settings_bind_with_mapping (enhancer_settings, pspec->name, row, + bind_prop, G_SETTINGS_BIND_DEFAULT, + (GSettingsBindGetMapping) _get_enum_mapping, + (GSettingsBindSetMapping) _set_enum_mapping, + pspec, NULL); + } else if (!is_flags) { + g_settings_bind (enhancer_settings, pspec->name, row, + bind_prop, G_SETTINGS_BIND_DEFAULT); + } + + reset_data = g_new (ClapperAppPreferencesResetData, 1); + reset_data->settings = g_object_ref (enhancer_settings); + reset_data->key = pspec->name; + + GST_TRACE ("Created reset button data: %p", reset_data); + + g_signal_connect_data (reset_button, "clicked", + G_CALLBACK (_reset_button_clicked_cb), reset_data, + (GClosureNotify) _reset_button_closure, G_CONNECT_DEFAULT); + + adw_preferences_group_add (self->enhancer_config_group, row); + self->enhancer_pspec_rows = g_list_append (self->enhancer_pspec_rows, row); + + return TRUE; +} + +static void +selected_enhancer_changed_cb (AdwComboRow *combo_row, + GParamSpec *pspec G_GNUC_UNUSED, ClapperAppPreferencesWindow *self) +{ + guint selected = adw_combo_row_get_selected (combo_row); + + /* Remove old rows */ + if (self->enhancer_pspec_rows) { + GList *el; + + for (el = self->enhancer_pspec_rows; el; el = g_list_next (el)) + adw_preferences_group_remove (self->enhancer_config_group, GTK_WIDGET (el->data)); + + g_clear_list (&self->enhancer_pspec_rows, NULL); + } + + /* Add new rows */ + if (selected != GTK_INVALID_LIST_POSITION) { + ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies (); + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, selected); + GParamSpec **pspecs; + guint n_pspecs; + gboolean has_props = FALSE; + + if ((pspecs = clapper_enhancer_proxy_get_target_properties (proxy, &n_pspecs))) { + GSettings *enhancer_settings = NULL; + guint i; + + for (i = 0; i < n_pspecs; ++i) { + if (pspecs[i]->flags & CLAPPER_ENHANCER_PARAM_GLOBAL) { + if (!enhancer_settings) + enhancer_settings = clapper_enhancer_proxy_get_settings (proxy); + if (enhancer_settings) + has_props |= _add_enhancer_config_row (self, pspecs[i], enhancer_settings); + } + } + + g_clear_object (&enhancer_settings); + } + + if (!has_props) { + GtkWidget *row = adw_action_row_new (); + + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("No configurable properties")); + adw_preferences_group_add (self->enhancer_config_group, row); + self->enhancer_pspec_rows = g_list_append (self->enhancer_pspec_rows, row); + } + } +} + +static void +enhancers_config_activated_cb (AdwActionRow *action_row, ClapperAppPreferencesWindow *self) +{ + /* If no model set yet */ + if (!adw_combo_row_get_model (self->enhancers_combo_row)) { + ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies (); + + adw_combo_row_set_model (self->enhancers_combo_row, G_LIST_MODEL (proxies)); + adw_combo_row_set_selected (self->enhancers_combo_row, GTK_INVALID_LIST_POSITION); + + GST_DEBUG ("Populated names combo row in enhancers subpage"); + + if (clapper_enhancer_proxy_list_get_n_proxies (proxies) > 0) + gtk_stack_set_visible_child (self->enhancers_stack, self->browse_enhancers_page); + else + gtk_stack_set_visible_child (self->enhancers_stack, self->no_enhancers_page); + } + + adw_preferences_window_push_subpage (ADW_PREFERENCES_WINDOW (self), self->enhancers_subpage); +} + /* Sort by plugin name and if the same, sort by element name */ static gint _compare_plugins_cb (gconstpointer ptr_a, gconstpointer ptr_b) @@ -301,6 +666,12 @@ _make_plugin_features_string_list (ClapperAppPreferencesWindow *self, const gcha return features_list; } +static gboolean +list_has_selection_closure (ClapperAppPreferencesWindow *self, guint selected) +{ + return (selected != GTK_INVALID_LIST_POSITION); +} + static GtkStringList * ranking_features_model_closure (ClapperAppPreferencesWindow *self, GtkStringObject *string_obj) { @@ -543,6 +914,7 @@ clapper_app_preferences_window_finalize (GObject *object) g_object_unref (self->settings); + g_clear_list (&self->enhancer_pspec_rows, NULL); g_clear_object (&self->plugins_list); if (self->features) @@ -598,6 +970,14 @@ clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *kla gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, subtitle_offset_spin_row); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, font_dialog_button); + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_stack); + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, browse_enhancers_page); + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, no_enhancers_page); + + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_subpage); + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_combo_row); + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancer_config_group); + gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, plugins_subpage); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, plugins_combo_row); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, features_combo_row); @@ -605,9 +985,13 @@ clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *kla gtk_widget_class_bind_template_callback (widget_class, seek_method_name_closure); + gtk_widget_class_bind_template_callback (widget_class, enhancers_config_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, selected_enhancer_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, plugin_ranking_activated_cb); gtk_widget_class_bind_template_callback (widget_class, plugin_ranking_unrealize_cb); + gtk_widget_class_bind_template_callback (widget_class, list_has_selection_closure); gtk_widget_class_bind_template_callback (widget_class, ranking_features_model_closure); gtk_widget_class_bind_template_callback (widget_class, add_override_button_sensitive_closure); gtk_widget_class_bind_template_callback (widget_class, add_override_button_clicked_cb); diff --git a/src/bin/clapper-app/css/styles.css b/src/bin/clapper-app/css/styles.css index 0f2e2293..7614f1c8 100644 --- a/src/bin/clapper-app/css/styles.css +++ b/src/bin/clapper-app/css/styles.css @@ -21,10 +21,10 @@ window.info .subcontent streamlist preferencesgroup { window.preferences .subcontent { margin: 16px; } -window.preferences .pluginssubpage .subcontent popover { +window.preferences .configsubpage .subcontent popover { min-width: 264px; } -window.preferences .pluginssubpage .subcontent button.pill { +window.preferences .configsubpage .subcontent button.pill { margin-top: 8px; margin-bottom: 8px; } diff --git a/src/bin/clapper-app/ui/clapper-app-preferences-window.ui b/src/bin/clapper-app/ui/clapper-app-preferences-window.ui index af643712..577c6d7a 100644 --- a/src/bin/clapper-app/ui/clapper-app-preferences-window.ui +++ b/src/bin/clapper-app/ui/clapper-app-preferences-window.ui @@ -136,6 +136,24 @@ Tweaks applications-engineering-symbolic + + + Clapper + + + Enhancers + Browse and configure properties of available enhancers + true + + + + go-next-symbolic + + + + + + GStreamer @@ -160,6 +178,124 @@ + + Clapper Enhancers + + + + + + + + + + + + + + + vertical + + + Available enhancers + Select an enhancer plugin to view its information and properties to configure. + + + Enhancer + true + + + + + + + + + + + vertical + + + enhancers_combo_row + + + + + Information + + + Module + + + enhancers_combo_row + + + + + + + + Description + + + enhancers_combo_row + + + + + + + + Version + + + enhancers_combo_row + + + + + + + + + + Properties + + + + + + + + + true + true + edit-find-symbolic + No Clapper Enhancers Found + Install some to add more cool functionalities to the player! + + + + + + + + + + + + + Plugin Ranking @@ -240,7 +376,7 @@ diff --git a/src/lib/clapper/clapper.c b/src/lib/clapper/clapper-basic-functions.c similarity index 63% rename from src/lib/clapper/clapper.c rename to src/lib/clapper/clapper-basic-functions.c index dfc50955..32e58f45 100644 --- a/src/lib/clapper/clapper.c +++ b/src/lib/clapper/clapper-basic-functions.c @@ -1,5 +1,5 @@ /* Clapper Playback Library - * Copyright (C) 2024 Rafał Dzięgiel + * Copyright (C) 2025 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,17 +22,22 @@ #include #include -#include "clapper.h" +#include "clapper-basic-functions.h" +#include "clapper-cache-private.h" #include "clapper-utils-private.h" #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-features-bus-private.h" +#include "clapper-enhancer-proxy-list-private.h" #include "gst/clapper-plugin-private.h" +#include "clapper-functionalities-availability.h" + #if CLAPPER_WITH_ENHANCERS_LOADER #include "clapper-enhancers-loader-private.h" #endif +static ClapperEnhancerProxyList *_proxies = NULL; static gboolean is_initialized = FALSE; static GMutex init_lock; @@ -46,13 +51,16 @@ clapper_init_check_internal (int *argc, char **argv[]) gst_pb_utils_init (); + clapper_cache_initialize (); clapper_utils_initialize (); clapper_playbin_bus_initialize (); clapper_app_bus_initialize (); clapper_features_bus_initialize (); + _proxies = clapper_enhancer_proxy_list_new_named ("global-proxy-list"); + #if CLAPPER_WITH_ENHANCERS_LOADER - clapper_enhancers_loader_initialize (); + clapper_enhancers_loader_initialize (_proxies); #endif gst_plugin_register_static ( @@ -149,18 +157,75 @@ clapper_init_check (int *argc, char **argv[]) * Returns: whether a plausible enhancer was found. * * Since: 0.8 + * + * Deprecated: 0.10: Use list of enhancer proxies from [func@Clapper.get_global_enhancer_proxies] or + * [property@Clapper.Player:enhancer-proxies] and check if any proxy matches your search criteria. */ gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name) { - gboolean success = FALSE; + gboolean is_https; + guint i, n_proxies; g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_type), FALSE); g_return_val_if_fail (scheme != NULL, FALSE); -#if CLAPPER_WITH_ENHANCERS_LOADER - success = clapper_enhancers_loader_check (iface_type, scheme, host, name); -#endif + if (host) { + /* Strip common subdomains, so plugins do not + * have to list all combinations */ + if (g_str_has_prefix (host, "www.")) + host += 4; + else if (g_str_has_prefix (host, "m.")) + host += 2; + } - return success; + /* Whether "http(s)" scheme is used */ + is_https = (g_str_has_prefix (scheme, "http") + && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))); + + if (!host && is_https) + return FALSE; + + n_proxies = clapper_enhancer_proxy_list_get_n_proxies (_proxies); + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (_proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type) + && clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme) + && (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) { + if (name) + *name = clapper_enhancer_proxy_get_friendly_name (proxy); + + return TRUE; + } + } + + return FALSE; +} + +/** + * clapper_get_global_enhancer_proxies: + * + * Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects. + * + * This returns a global list of enhancer proxy objects. You can use it to inspect + * available enhancers without creating a new player instance. + * + * Remember to initialize Clapper library before using this function. + * + * Only enhancer properties with [flags@Clapper.EnhancerParamFlags.GLOBAL] flag can be + * set on proxies in this list. These are meant to be set ONLY by users, not applications + * as they carry over to all player instances (possibly including other apps). Applications + * should instead be changing properties with [flags@Clapper.EnhancerParamFlags.LOCAL] flag + * set from individual proxy lists from [property@Clapper.Player:enhancer-proxies] which + * will affect only that single player instance given list belongs to. + * + * Returns: (transfer none): a global #ClapperEnhancerProxyList of enhancer proxies. + * + * Since: 0.10 + */ +ClapperEnhancerProxyList * +clapper_get_global_enhancer_proxies (void) +{ + return _proxies; } diff --git a/src/lib/clapper/clapper-basic-functions.h b/src/lib/clapper/clapper-basic-functions.h new file mode 100644 index 00000000..2aacc876 --- /dev/null +++ b/src/lib/clapper/clapper-basic-functions.h @@ -0,0 +1,46 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +#include +#include + +G_BEGIN_DECLS + +CLAPPER_API +void clapper_init (int *argc, char **argv[]); + +CLAPPER_API +gboolean clapper_init_check (int *argc, char **argv[]); + +CLAPPER_DEPRECATED +gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); + +CLAPPER_API +ClapperEnhancerProxyList * clapper_get_global_enhancer_proxies (void); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-cache-private.h b/src/lib/clapper/clapper-cache-private.h new file mode 100644 index 00000000..66c41ab4 --- /dev/null +++ b/src/lib/clapper/clapper-cache-private.h @@ -0,0 +1,93 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void clapper_cache_initialize (void); + +G_GNUC_INTERNAL +GMappedFile * clapper_cache_open (const gchar *filename, const gchar **data, GError **error); + +G_GNUC_INTERNAL +gboolean clapper_cache_read_boolean (const gchar **data); + +G_GNUC_INTERNAL +gint clapper_cache_read_int (const gchar **data); + +G_GNUC_INTERNAL +guint clapper_cache_read_uint (const gchar **data); + +G_GNUC_INTERNAL +gdouble clapper_cache_read_double (const gchar **data); + +G_GNUC_INTERNAL +const gchar * clapper_cache_read_string (const gchar **data); + +G_GNUC_INTERNAL +GType clapper_cache_read_enum (const gchar **data); + +G_GNUC_INTERNAL +GType clapper_cache_read_flags (const gchar **data); + +G_GNUC_INTERNAL +GType clapper_cache_read_iface (const gchar **data); + +G_GNUC_INTERNAL +GParamSpec * clapper_cache_read_pspec (const gchar **data); + +G_GNUC_INTERNAL +GByteArray * clapper_cache_create (void); + +G_GNUC_INTERNAL +void clapper_cache_store_boolean (GByteArray *bytes, gboolean val); + +G_GNUC_INTERNAL +void clapper_cache_store_int (GByteArray *bytes, gint val); + +G_GNUC_INTERNAL +void clapper_cache_store_uint (GByteArray *bytes, guint val); + +G_GNUC_INTERNAL +void clapper_cache_store_double (GByteArray *bytes, gdouble val); + +G_GNUC_INTERNAL +void clapper_cache_store_string (GByteArray *bytes, const gchar *val); + +G_GNUC_INTERNAL +void clapper_cache_store_enum (GByteArray *bytes, GType enum_type); + +G_GNUC_INTERNAL +void clapper_cache_store_flags (GByteArray *bytes, GType flags_type); + +G_GNUC_INTERNAL +gboolean clapper_cache_store_iface (GByteArray *bytes, GType iface); + +G_GNUC_INTERNAL +gboolean clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec); + +G_GNUC_INTERNAL +gboolean clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-cache.c b/src/lib/clapper/clapper-cache.c new file mode 100644 index 00000000..efd21902 --- /dev/null +++ b/src/lib/clapper/clapper-cache.c @@ -0,0 +1,491 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "clapper-cache-private.h" +#include "clapper-version.h" +#include "clapper-extractable.h" + +#define CLAPPER_CACHE_HEADER "CLAPPER" + +typedef enum +{ + CLAPPER_CACHE_IFACE_EXTRACTABLE = 1, +} ClapperCacheIfaces; + +static GArray *enum_registry = NULL; +static GArray *flags_registry = NULL; +static gboolean cache_disabled = FALSE; + +void +clapper_cache_initialize (void) +{ + const gchar *env = g_getenv ("CLAPPER_DISABLE_CACHE"); + + if (G_LIKELY (!env || !g_str_has_prefix (env, "1"))) { + enum_registry = g_array_new (FALSE, TRUE, sizeof (GEnumValue *)); + flags_registry = g_array_new (FALSE, TRUE, sizeof (GFlagsValue *)); + } else { + cache_disabled = TRUE; + } +} + +GMappedFile * +clapper_cache_open (const gchar *filename, const gchar **data, GError **error) +{ + GMappedFile *file; + + if (G_UNLIKELY (cache_disabled)) + return NULL; + + if (!(file = g_mapped_file_new (filename, FALSE, error))) + return NULL; + + if (G_UNLIKELY (g_mapped_file_get_length (file) == 0)) { + g_mapped_file_unref (file); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "File is empty"); + return NULL; + } + + *data = g_mapped_file_get_contents (file); + + /* Header name check */ + if (G_UNLIKELY (g_strcmp0 (*data, CLAPPER_CACHE_HEADER) != 0)) { + g_mapped_file_unref (file); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Invalid file header"); + return NULL; + } + *data += strlen (*data) + 1; + + /* Header version check */ + if (clapper_cache_read_uint (data) != CLAPPER_VERSION_HEX) { + g_mapped_file_unref (file); + /* Just different version, so no error set */ + return NULL; + } + + return file; +} + +inline gboolean +clapper_cache_read_boolean (const gchar **data) +{ + gboolean val = *(const gboolean *) *data; + *data += sizeof (gboolean); + + return val; +} + +inline gint +clapper_cache_read_int (const gchar **data) +{ + gint val = *(const gint *) *data; + *data += sizeof (gint); + + return val; +} + +inline guint +clapper_cache_read_uint (const gchar **data) +{ + guint val = *(const guint *) *data; + *data += sizeof (guint); + + return val; +} + +inline gdouble +clapper_cache_read_double (const gchar **data) +{ + gdouble val = *(const gdouble *) *data; + *data += sizeof (gdouble); + + return val; +} + +inline const gchar * +clapper_cache_read_string (const gchar **data) +{ + const gboolean is_null = clapper_cache_read_boolean (data); + const gchar *str = NULL; + + if (!is_null) { + str = *data; + *data += strlen (str) + 1; + } + + return str; +} + +inline GType +clapper_cache_read_enum (const gchar **data) +{ + GType type; + const gchar *enum_name; + guint i, n_values; + + enum_name = clapper_cache_read_string (data); + n_values = clapper_cache_read_uint (data); + + /* If not registered yet */ + if ((type = g_type_from_name (enum_name)) == 0) { + GEnumValue *values = g_new0 (GEnumValue, n_values + 1); + + for (i = 0; i < n_values; ++i) { + values[i].value = clapper_cache_read_int (data); + values[i].value_name = g_intern_string (clapper_cache_read_string (data)); + values[i].value_nick = g_intern_string (clapper_cache_read_string (data)); + } + g_array_append_val (enum_registry, values); // store statically + + type = g_enum_register_static (g_intern_string (enum_name), + g_array_index (enum_registry, GEnumValue *, enum_registry->len - 1)); + } else { + /* Skip over data */ + for (i = 0; i < n_values; ++i) { + clapper_cache_read_int (data); // value + clapper_cache_read_string (data); // value_name + clapper_cache_read_string (data); // value_nick + } + } + + return type; +} + +inline GType +clapper_cache_read_flags (const gchar **data) +{ + GType type; + const gchar *flags_name; + guint i, n_values; + + flags_name = clapper_cache_read_string (data); + n_values = clapper_cache_read_uint (data); + + /* If not registered yet */ + if ((type = g_type_from_name (flags_name)) == 0) { + GFlagsValue *values = g_new0 (GFlagsValue, n_values + 1); + + for (i = 0; i < n_values; ++i) { + values[i].value = clapper_cache_read_int (data); + values[i].value_name = g_intern_string (clapper_cache_read_string (data)); + values[i].value_nick = g_intern_string (clapper_cache_read_string (data)); + } + g_array_append_val (flags_registry, values); // store statically + + type = g_flags_register_static (g_intern_string (flags_name), + g_array_index (flags_registry, GFlagsValue *, flags_registry->len - 1)); + } else { + /* Skip over data */ + for (i = 0; i < n_values; ++i) { + clapper_cache_read_int (data); // value + clapper_cache_read_string (data); // value_name + clapper_cache_read_string (data); // value_nick + } + } + + return type; +} + +GType +clapper_cache_read_iface (const gchar **data) +{ + gint iface_id = clapper_cache_read_int (data); + + switch (iface_id) { + case CLAPPER_CACHE_IFACE_EXTRACTABLE: + return CLAPPER_TYPE_EXTRACTABLE; + default: + return 0; + } +} + +GParamSpec * +clapper_cache_read_pspec (const gchar **data) +{ + GParamSpec *pspec; + GType value_type; + const gchar *name, *nick, *blurb; + GParamFlags flags; + + value_type = *(const GType *) *data; + *data += sizeof (GType); + + name = clapper_cache_read_string (data); + nick = clapper_cache_read_string (data); + blurb = clapper_cache_read_string (data); + + flags = *(const GParamFlags *) *data; + *data += sizeof (GParamFlags); + + /* NOTE: C does not guarantee order in which function arguments + * are evaluated, so read into variables and then create pspec */ + + switch (value_type) { + case G_TYPE_BOOLEAN: + pspec = g_param_spec_boolean (name, nick, blurb, + clapper_cache_read_boolean (data), flags); + break; + case G_TYPE_INT:{ + gint minimum = clapper_cache_read_int (data); + gint maximum = clapper_cache_read_int (data); + gint default_value = clapper_cache_read_int (data); + + pspec = g_param_spec_int (name, nick, blurb, + minimum, maximum, default_value, flags); + break; + } + case G_TYPE_UINT:{ + guint minimum = clapper_cache_read_uint (data); + guint maximum = clapper_cache_read_uint (data); + guint default_value = clapper_cache_read_uint (data); + + pspec = g_param_spec_uint (name, nick, blurb, + minimum, maximum, default_value, flags); + break; + } + case G_TYPE_DOUBLE:{ + gdouble minimum = clapper_cache_read_double (data); + gdouble maximum = clapper_cache_read_double (data); + gdouble default_value = clapper_cache_read_double (data); + + pspec = g_param_spec_double (name, nick, blurb, + minimum, maximum, default_value, flags); + break; + } + case G_TYPE_STRING: + pspec = g_param_spec_string (name, nick, blurb, + clapper_cache_read_string (data), flags); + break; + case G_TYPE_ENUM:{ + GType enum_type = clapper_cache_read_enum (data); + gint default_value = clapper_cache_read_int (data); + + pspec = g_param_spec_enum (name, nick, blurb, + enum_type, default_value, flags); + break; + } + case G_TYPE_FLAGS:{ + GType flags_type = clapper_cache_read_flags (data); + guint default_value = clapper_cache_read_uint (data); + + pspec = g_param_spec_flags (name, nick, blurb, + flags_type, default_value, flags); + break; + } + default: + return NULL; + } + + return g_param_spec_ref_sink (pspec); +} + +GByteArray * +clapper_cache_create (void) +{ + GByteArray *bytes; + + if (G_UNLIKELY (cache_disabled)) + return NULL; + + bytes = g_byte_array_new (); + + /* NOTE: We do not store whether string is NULL here, since it never is */ + g_byte_array_append (bytes, (const guint8 *) CLAPPER_CACHE_HEADER, 8); // 7 + 1 + clapper_cache_store_uint (bytes, CLAPPER_VERSION_HEX); + + return bytes; +} + +inline void +clapper_cache_store_boolean (GByteArray *bytes, gboolean val) +{ + g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gboolean)); +} + +inline void +clapper_cache_store_int (GByteArray *bytes, gint val) +{ + g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint)); +} + +inline void +clapper_cache_store_uint (GByteArray *bytes, guint val) +{ + g_byte_array_append (bytes, (const guint8 *) &val, sizeof (guint)); +} + +inline void +clapper_cache_store_double (GByteArray *bytes, gdouble val) +{ + g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gdouble)); +} + +inline void +clapper_cache_store_string (GByteArray *bytes, const gchar *val) +{ + /* Distinguish empty string from NULL */ + const gboolean is_null = (val == NULL); + + clapper_cache_store_boolean (bytes, is_null); + if (!is_null) + g_byte_array_append (bytes, (const guint8 *) val, strlen (val) + 1); +} + +inline void +clapper_cache_store_enum (GByteArray *bytes, GType enum_type) +{ + GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (enum_type)); + guint i; + + clapper_cache_store_string (bytes, g_type_name (enum_type)); + clapper_cache_store_uint (bytes, enum_class->n_values); + + for (i = 0; i < enum_class->n_values; ++i) { + clapper_cache_store_int (bytes, enum_class->values[i].value); + clapper_cache_store_string (bytes, enum_class->values[i].value_name); + clapper_cache_store_string (bytes, enum_class->values[i].value_nick); + } +} + +inline void +clapper_cache_store_flags (GByteArray *bytes, GType flags_type) +{ + GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (flags_type)); + guint i; + + clapper_cache_store_string (bytes, g_type_name (flags_type)); + clapper_cache_store_uint (bytes, flags_class->n_values); + + for (i = 0; i < flags_class->n_values; ++i) { + clapper_cache_store_int (bytes, flags_class->values[i].value); + clapper_cache_store_string (bytes, flags_class->values[i].value_name); + clapper_cache_store_string (bytes, flags_class->values[i].value_nick); + } +} + +gboolean +clapper_cache_store_iface (GByteArray *bytes, GType iface) +{ + gint iface_id = 0; + + if (iface == CLAPPER_TYPE_EXTRACTABLE) + iface_id = CLAPPER_CACHE_IFACE_EXTRACTABLE; + else + return FALSE; + + clapper_cache_store_int (bytes, iface_id); + return TRUE; +} + +gboolean +clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec) +{ + GParamFlags flags; + const gboolean is_enum = G_IS_PARAM_SPEC_ENUM (pspec); + const gboolean is_flags = (!is_enum && G_IS_PARAM_SPEC_FLAGS (pspec)); + + if (is_enum) { + GType enum_type = G_TYPE_ENUM; + g_byte_array_append (bytes, (const guint8 *) &enum_type, sizeof (GType)); + } else if (is_flags) { + GType flags_type = G_TYPE_FLAGS; + g_byte_array_append (bytes, (const guint8 *) &flags_type, sizeof (GType)); + } else { + g_byte_array_append (bytes, (const guint8 *) &pspec->value_type, sizeof (GType)); + } + + clapper_cache_store_string (bytes, g_param_spec_get_name (pspec)); + clapper_cache_store_string (bytes, g_param_spec_get_nick (pspec)); + clapper_cache_store_string (bytes, g_param_spec_get_blurb (pspec)); + + flags = pspec->flags; + flags &= ~G_PARAM_STATIC_STRINGS; // Data read from cache is never static + g_byte_array_append (bytes, (const guint8 *) &flags, sizeof (GParamFlags)); + + switch (pspec->value_type) { + case G_TYPE_BOOLEAN:{ + GParamSpecBoolean *p = (GParamSpecBoolean *) pspec; + clapper_cache_store_boolean (bytes, p->default_value); + break; + } + case G_TYPE_INT:{ + GParamSpecInt *p = (GParamSpecInt *) pspec; + clapper_cache_store_int (bytes, p->minimum); + clapper_cache_store_int (bytes, p->maximum); + clapper_cache_store_int (bytes, p->default_value); + break; + } + case G_TYPE_UINT:{ + GParamSpecUInt *p = (GParamSpecUInt *) pspec; + clapper_cache_store_uint (bytes, p->minimum); + clapper_cache_store_uint (bytes, p->maximum); + clapper_cache_store_uint (bytes, p->default_value); + break; + } + case G_TYPE_DOUBLE:{ + GParamSpecDouble *p = (GParamSpecDouble *) pspec; + clapper_cache_store_double (bytes, p->minimum); + clapper_cache_store_double (bytes, p->maximum); + clapper_cache_store_double (bytes, p->default_value); + break; + } + case G_TYPE_STRING:{ + GParamSpecString *p = (GParamSpecString *) pspec; + clapper_cache_store_string (bytes, p->default_value); + break; + } + default:{ + if (is_enum) { + GParamSpecEnum *p = (GParamSpecEnum *) pspec; + clapper_cache_store_enum (bytes, pspec->value_type); + clapper_cache_store_int (bytes, p->default_value); + break; + } else if (is_flags) { + GParamSpecFlags *p = (GParamSpecFlags *) pspec; + clapper_cache_store_flags (bytes, pspec->value_type); + clapper_cache_store_uint (bytes, p->default_value); + break; + } + return FALSE; + } + } + + return TRUE; +} + +gboolean +clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error) +{ + gchar *dirname = g_path_get_dirname (filename); + gboolean has_dir; + + has_dir = (g_mkdir_with_parents (dirname, 0755) == 0); + g_free (dirname); + + if (!has_dir) { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Could not create directory to store cache content"); + return FALSE; + } + + /* Using "g_file_set_contents" to replace file atomically */ + return g_file_set_contents (filename, (const gchar *) bytes->data, bytes->len, error); +} diff --git a/src/lib/clapper/clapper-enhancer-proxy-list-private.h b/src/lib/clapper/clapper-enhancer-proxy-list-private.h new file mode 100644 index 00000000..c25b4024 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-list-private.h @@ -0,0 +1,41 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#include "clapper-enhancer-proxy-list.h" +#include "clapper-enhancer-proxy.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +ClapperEnhancerProxyList * clapper_enhancer_proxy_list_new_named (const gchar *name); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *list, ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *list); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *list); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy-list.c b/src/lib/clapper/clapper-enhancer-proxy-list.c new file mode 100644 index 00000000..619436bd --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-list.c @@ -0,0 +1,329 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * ClapperEnhancerProxyList: + * + * A list of enhancer proxies. + * + * Since: 0.10 + */ + +#include + +#include "clapper-basic-functions.h" +#include "clapper-enhancer-proxy-list-private.h" +#include "clapper-enhancer-proxy-private.h" + +#define GST_CAT_DEFAULT clapper_enhancer_proxy_list_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperEnhancerProxyList +{ + GstObject parent; + + GPtrArray *proxies; +}; + +enum +{ + PROP_0, + PROP_N_PROXIES, + PROP_LAST +}; + +static void clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface); + +#define parent_class clapper_enhancer_proxy_list_parent_class +G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, GST_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_enhancer_proxy_list_model_iface_init)); + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static GType +clapper_enhancer_proxy_list_model_get_item_type (GListModel *model) +{ + return CLAPPER_TYPE_ENHANCER_PROXY; +} + +static guint +clapper_enhancer_proxy_list_model_get_n_items (GListModel *model) +{ + return CLAPPER_ENHANCER_PROXY_LIST_CAST (model)->proxies->len; +} + +static gpointer +clapper_enhancer_proxy_list_model_get_item (GListModel *model, guint index) +{ + ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (model); + ClapperEnhancerProxy *proxy = NULL; + + if (G_LIKELY (index < self->proxies->len)) + proxy = gst_object_ref (g_ptr_array_index (self->proxies, index)); + + return proxy; +} + +static void +clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = clapper_enhancer_proxy_list_model_get_item_type; + iface->get_n_items = clapper_enhancer_proxy_list_model_get_n_items; + iface->get_item = clapper_enhancer_proxy_list_model_get_item; +} + +/* + * clapper_enhancer_proxy_list_new_named: + * @name: (nullable): name of the #GstObject + * + * Returns: (transfer full): a new #ClapperEnhancerProxyList instance + */ +ClapperEnhancerProxyList * +clapper_enhancer_proxy_list_new_named (const gchar *name) +{ + ClapperEnhancerProxyList *list; + + list = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY_LIST, + "name", name, NULL); + gst_object_ref_sink (list); + + return list; +} + +void +clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *self, ClapperEnhancerProxy *proxy) +{ + g_ptr_array_add (self->proxies, proxy); + gst_object_set_parent (GST_OBJECT_CAST (proxy), GST_OBJECT_CAST (self)); +} + +/* + * clapper_enhancer_proxy_list_fill_from_global_proxies: + * + * Fill list with unconfigured proxies from global proxies list. + */ +void +clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *self) +{ + ClapperEnhancerProxyList *global_list = clapper_get_global_enhancer_proxies (); + static guint _list_id = 0; + guint i; + + for (i = 0; i < global_list->proxies->len; ++i) { + ClapperEnhancerProxy *proxy, *proxy_copy; + gchar obj_name[64]; + + proxy = clapper_enhancer_proxy_list_peek_proxy (global_list, i); + + /* Name newly created proxy, very useful for debugging. Keep index per + * list, so it will be the same as the player that proxy belongs to. */ + g_snprintf (obj_name, sizeof (obj_name), "%s-proxy%u", + clapper_enhancer_proxy_get_friendly_name (proxy), _list_id); + proxy_copy = clapper_enhancer_proxy_copy (proxy, obj_name); + + clapper_enhancer_proxy_list_take_proxy (self, proxy_copy); + } + _list_id++; +} + +static gint +_sort_values_by_name (ClapperEnhancerProxy *proxy_a, ClapperEnhancerProxy *proxy_b) +{ + return g_ascii_strcasecmp ( + clapper_enhancer_proxy_get_friendly_name (proxy_a), + clapper_enhancer_proxy_get_friendly_name (proxy_b)); +} + +/* + * clapper_enhancer_proxy_list_sort: + * + * Sort all list elements by enhancer friendly name. + */ +void +clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *self) +{ + g_ptr_array_sort_values (self->proxies, (GCompareFunc) _sort_values_by_name); +} + +/** + * clapper_enhancer_proxy_list_get_proxy: + * @list: a #ClapperEnhancerProxyList + * @index: an enhancer proxy index + * + * Get the #ClapperEnhancerProxy at index. + * + * This behaves the same as [method@Gio.ListModel.get_item], and is here + * for code uniformity and convenience to avoid type casting by user. + * + * Returns: (transfer full) (nullable): The #ClapperEnhancerProxy at @index. + * + * Since: 0.10 + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *self, guint index) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL); + + return g_list_model_get_item (G_LIST_MODEL (self), index); +} + +/** + * clapper_enhancer_proxy_list_peek_proxy: (skip) + * @list: a #ClapperEnhancerProxyList + * @index: an enhancer proxy index + * + * Get the #ClapperEnhancerProxy at index. + * + * Similar to [method@Clapper.EnhancerProxyList.get_proxy], but does not take + * a new reference on proxy. + * + * Proxies in a list are only removed when a [class@Clapper.Player] instance + * they originate from is destroyed, so do not use returned object afterwards + * unless you take an additional reference on it. + * + * Returns: (transfer none) (nullable): The #ClapperEnhancerProxy at @index. + * + * Since: 0.10 + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *self, guint index) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL); + + return g_ptr_array_index (self->proxies, index); +} + +/** + * clapper_enhancer_proxy_list_get_proxy_by_module: + * @list: a #ClapperEnhancerProxyList + * @module_name: an enhancer module name + * + * Get the #ClapperEnhancerProxy by module name as defined in its plugin file. + * + * A convenience function to find a #ClapperEnhancerProxy by its unique + * module name in the list. + * + * Returns: (transfer full) (nullable): The #ClapperEnhancerProxy with requested module name. + * + * Since: 0.10 + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *self, const gchar *module_name) +{ + guint i; + + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL); + g_return_val_if_fail (module_name != NULL, NULL); + + for (i = 0; i < self->proxies->len; ++i) { + ClapperEnhancerProxy *proxy = g_ptr_array_index (self->proxies, i); + + if (strcmp (clapper_enhancer_proxy_get_module_name (proxy), module_name) == 0) + return gst_object_ref (proxy); + } + + return NULL; +} + +/** + * clapper_enhancer_proxy_list_get_n_proxies: + * @list: a #ClapperEnhancerProxyList + * + * Get the number of proxies in #ClapperEnhancerProxyList. + * + * This behaves the same as [method@Gio.ListModel.get_n_items], and is here + * for code uniformity and convenience to avoid type casting by user. + * + * Returns: The number of proxies in #ClapperEnhancerProxyList. + * + * Since: 0.10 + */ +guint +clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), 0); + + return g_list_model_get_n_items (G_LIST_MODEL (self)); +} + +static void +_proxy_remove_func (ClapperEnhancerProxy *proxy) +{ + gst_object_unparent (GST_OBJECT_CAST (proxy)); + gst_object_unref (proxy); +} + +static void +clapper_enhancer_proxy_list_init (ClapperEnhancerProxyList *self) +{ + self->proxies = g_ptr_array_new_with_free_func ((GDestroyNotify) _proxy_remove_func); +} + +static void +clapper_enhancer_proxy_list_finalize (GObject *object) +{ + ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_ptr_array_unref (self->proxies); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_enhancer_proxy_list_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object); + + switch (prop_id) { + case PROP_N_PROXIES: + g_value_set_uint (value, clapper_enhancer_proxy_list_get_n_proxies (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_enhancer_proxy_list_class_init (ClapperEnhancerProxyListClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerproxylist", 0, + "Clapper Enhancer Proxy List"); + + gobject_class->get_property = clapper_enhancer_proxy_list_get_property; + gobject_class->finalize = clapper_enhancer_proxy_list_finalize; + + /** + * ClapperEnhancerProxyList:n-proxies: + * + * Number of proxies in the list. + * + * Since: 0.10 + */ + param_specs[PROP_N_PROXIES] = g_param_spec_uint ("n-proxies", + NULL, NULL, 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} diff --git a/src/lib/clapper/clapper-enhancer-proxy-list.h b/src/lib/clapper/clapper-enhancer-proxy-list.h new file mode 100644 index 00000000..eb810d2a --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-list.h @@ -0,0 +1,53 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_ENHANCER_PROXY_LIST (clapper_enhancer_proxy_list_get_type()) +#define CLAPPER_ENHANCER_PROXY_LIST_CAST(obj) ((ClapperEnhancerProxyList *)(obj)) + +CLAPPER_API +G_DECLARE_FINAL_TYPE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, CLAPPER, ENHANCER_PROXY_LIST, GstObject) + +CLAPPER_API +ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *list, guint index); + +CLAPPER_API +ClapperEnhancerProxy * clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *list, guint index); + +CLAPPER_API +ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *list, const gchar *module_name); + +CLAPPER_API +guint clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *list); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy-private.h b/src/lib/clapper/clapper-enhancer-proxy-private.h new file mode 100644 index 00000000..144a9c2d --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-private.h @@ -0,0 +1,50 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +#include "clapper-enhancer-proxy.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +ClapperEnhancerProxy * clapper_enhancer_proxy_new_global_take (GObject *peas_info); // Using parent type for building without libpeas + +G_GNUC_INTERNAL +ClapperEnhancerProxy * clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_name); + +G_GNUC_INTERNAL +gboolean clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +gboolean clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *proxy, GObject *enhancer); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_export_to_cache (ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +GObject * clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_apply_current_config_to_enhancer (ClapperEnhancerProxy *proxy, GObject *enhancer); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy.c b/src/lib/clapper/clapper-enhancer-proxy.c new file mode 100644 index 00000000..cd70a9ce --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy.c @@ -0,0 +1,1205 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * ClapperEnhancerProxy: + * + * An intermediary between player and enhancer plugin. + * + * Applications can use this to inspect enhancer information, + * its properties and configure them. + * + * Clapper player manages all enhancers internally, including creating when + * needed and destroying them later. Instead, it provides access to so called + * enhancer proxy objects which allow to browse available enhancer properties + * and store their config either globally or locally for each player instance. + * + * Use [func@Clapper.get_global_enhancer_proxies] or [property@Clapper.Player:enhancer-proxies] + * property to access a [class@Clapper.EnhancerProxyList] of available enhancer proxies. While both + * lists include the same amount of proxies, the difference is which properties can be configured + * in which list. Only the latter allows tweaking of local (per player instance) properties using + * [method@Clapper.EnhancerProxy.set_locally] function. + * + * Since: 0.10 + */ + +#include "config.h" + +#include + +#include "clapper-enhancer-proxy-private.h" +#include "clapper-enhancer-proxy-list.h" +#include "clapper-basic-functions.h" +#include "clapper-cache-private.h" +#include "clapper-extractable.h" +#include "clapper-enums.h" + +#include "clapper-functionalities-availability.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include +#endif + +#define CONFIG_STRUCTURE_NAME "config" + +#define GST_CAT_DEFAULT clapper_enhancer_proxy_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperEnhancerProxy +{ + GstObject parent; + + /* Hold a ref on info to ensure + * below props are kept alive */ + GObject *peas_info; + + const gchar *friendly_name; + const gchar *module_name; + const gchar *module_dir; + const gchar *description; + const gchar *version; + + guint n_ifaces; + GType *ifaces; + + guint n_pspecs; + GParamSpec **pspecs; + + ClapperEnhancerParamFlags scope; + GstStructure *local_config; + + /* GSettings are not thread-safe, + * so store schema instead */ + GSettingsSchema *schema; + gboolean schema_init_done; +}; + +enum +{ + PROP_0, + PROP_FRIENDLY_NAME, + PROP_MODULE_NAME, + PROP_MODULE_DIR, + PROP_DESCRIPTION, + PROP_VERSION, + PROP_LAST +}; + +#define parent_class clapper_enhancer_proxy_parent_class +G_DEFINE_TYPE (ClapperEnhancerProxy, clapper_enhancer_proxy, GST_TYPE_OBJECT); + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static gboolean +_update_config_cb (GQuark field_id, const GValue *value, GstStructure *config) +{ + gst_structure_set_value (config, g_quark_to_string (field_id), value); + return TRUE; +} + +static void +_update_local_config_from_structure (ClapperEnhancerProxy *self, const GstStructure *src) +{ + GST_OBJECT_LOCK (self); + + if (!self->local_config) + self->local_config = gst_structure_copy (src); + else + gst_structure_foreach (src, (GstStructureForeachFunc) _update_config_cb, self->local_config); + + GST_OBJECT_UNLOCK (self); +} + +/* + * clapper_enhancer_proxy_new_global_take: + * @peas_info: (transfer full): a #PeasPluginInfo cast to GObject + * + * Create a new proxy. This should be only used for creating + * global proxies using @peas_info from enhancer loader, + * while player should use copies of global proxies. + * + * Returns: (transfer full): a new #ClapperEnhancerProxy instance. + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_new_global_take (GObject *peas_info) +{ + ClapperEnhancerProxy *proxy; + +#if CLAPPER_WITH_ENHANCERS_LOADER + const PeasPluginInfo *info = (const PeasPluginInfo *) peas_info; + gchar obj_name[64]; + const gchar *friendly_name; + + /* Name newly created proxy for easier debugging. Its best + * to do it with g_object_new(), as this avoid GStreamer + * naming it first with us renaming it afterwards. */ + friendly_name = peas_plugin_info_get_name (info); + g_snprintf (obj_name, sizeof (obj_name), "%s-global-proxy", friendly_name); + proxy = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY, "name", obj_name, NULL); + + proxy->peas_info = peas_info; + proxy->friendly_name = friendly_name; + proxy->module_name = peas_plugin_info_get_module_name (info); + proxy->module_dir = peas_plugin_info_get_module_dir (info); + proxy->description = peas_plugin_info_get_description (info); + proxy->version = peas_plugin_info_get_version (info); +#else + /* XXX: This should never be reached. We do not + * create proxies if we cannot load enhancers. */ + proxy = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY, NULL); +#endif + + proxy->scope = CLAPPER_ENHANCER_PARAM_GLOBAL; + + gst_object_ref_sink (proxy); + + return proxy; +} + +/* + * clapper_enhancer_proxy_copy: + * @src_proxy: a #ClapperEnhancerProxy to copy + * @copy_name: name of the #GstObject copy + * + * Create a copy of enhancer proxy. + * + * Using another proxy as source to avoids reading cache again. + * This is mainly for internal usage to create new unconfigured + * from global proxy list. + * + * Returns: (transfer full): a new #ClapperEnhancerProxy instance. + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_name) +{ + ClapperEnhancerProxy *copy; + guint i; + + copy = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY, + "name", copy_name, NULL); + + copy->peas_info = g_object_ref (src_proxy->peas_info); + copy->friendly_name = src_proxy->friendly_name; + copy->module_name = src_proxy->module_name; + copy->module_dir = src_proxy->module_dir; + copy->description = src_proxy->description; + copy->version = src_proxy->version; + + /* Copy extra data from source proxy */ + + copy->n_ifaces = src_proxy->n_ifaces; + copy->ifaces = g_new (GType, copy->n_ifaces); + + for (i = 0; i < src_proxy->n_ifaces; ++i) + copy->ifaces[i] = src_proxy->ifaces[i]; + + copy->n_pspecs = src_proxy->n_pspecs; + copy->pspecs = g_new (GParamSpec *, copy->n_pspecs); + + for (i = 0; i < src_proxy->n_pspecs; ++i) + copy->pspecs[i] = src_proxy->pspecs[i]; + + copy->scope = CLAPPER_ENHANCER_PARAM_LOCAL; + + GST_OBJECT_LOCK (src_proxy); + + if (src_proxy->schema) + copy->schema = g_settings_schema_ref (src_proxy->schema); + + copy->schema_init_done = src_proxy->schema_init_done; + + if (src_proxy->local_config) + copy->local_config = gst_structure_copy (src_proxy->local_config); + + GST_OBJECT_UNLOCK (src_proxy); + + gst_object_ref_sink (copy); + + return copy; +} + +static inline void +_init_schema (ClapperEnhancerProxy *self) +{ + GST_OBJECT_LOCK (self); + + if (self->schema_init_done) { + GST_OBJECT_UNLOCK (self); + return; + } + + if (self->scope == CLAPPER_ENHANCER_PARAM_GLOBAL) { + guint i; + gboolean configurable = FALSE; + + GST_TRACE_OBJECT (self, "Initializing settings schema"); + + /* Check whether to expect any schema without file query */ + for (i = 0; i < self->n_pspecs; ++i) { + if ((configurable = (self->pspecs[i]->flags & CLAPPER_ENHANCER_PARAM_GLOBAL))) + break; + } + + if (configurable) { + GSettingsSchemaSource *schema_source; + GError *error = NULL; + + schema_source = g_settings_schema_source_new_from_directory ( + self->module_dir, g_settings_schema_source_get_default (), TRUE, &error); + + if (!error) { + gchar **non_reloc = NULL; + + g_settings_schema_source_list_schemas (schema_source, + FALSE, &non_reloc, NULL); + + if (non_reloc && non_reloc[0]) { + const gchar *schema_id = non_reloc[0]; + + GST_DEBUG_OBJECT (self, "Found settings schema: %s", schema_id); + self->schema = g_settings_schema_source_lookup (schema_source, schema_id, FALSE); + } + + g_strfreev (non_reloc); + } else { + GST_ERROR_OBJECT (self, "Could not load settings, reason: %s", + GST_STR_NULL (error->message)); + g_error_free (error); + } + + /* SAFETY: Not sure if on error invalid source might be returned */ + g_clear_pointer (&schema_source, g_settings_schema_source_unref); + } + } else { + ClapperEnhancerProxyList *proxies; + ClapperEnhancerProxy *global_proxy; + + proxies = clapper_get_global_enhancer_proxies (); + global_proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, self->module_name); + + /* Must ensure init was done on global + * proxy before accessing its schema */ + _init_schema (global_proxy); + + /* Just ref the schema from global one, so we + * can avoid loading them in local proxies */ + if (global_proxy->schema) + self->schema = g_settings_schema_ref (global_proxy->schema); + + gst_object_unref (global_proxy); + } + + self->schema_init_done = TRUE; + + GST_OBJECT_UNLOCK (self); +} + +static inline gchar * +_build_cache_filename (ClapperEnhancerProxy *self) +{ + return g_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME, + "enhancers", self->module_name, "cache.bin", NULL); +} + +gboolean +clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *self) +{ + GMappedFile *mapped_file; + GError *error = NULL; + gchar *filename; + const gchar *data; + guint i; + + filename = _build_cache_filename (self); + mapped_file = clapper_cache_open (filename, &data, &error); + g_free (filename); + + if (!mapped_file) { + /* No error if cache disabled or version mismatch */ + if (error) { + if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) + GST_DEBUG_OBJECT (self, "No cache file found"); + else + GST_ERROR_OBJECT (self, "Could not restore from cache, reason: %s", error->message); + + g_error_free (error); + } + + return FALSE; + } + + /* Plugin version check */ + if (g_strcmp0 (clapper_cache_read_string (&data), self->version) != 0) + return FALSE; // not an error + + /* Restore Interfaces */ + if ((self->n_ifaces = clapper_cache_read_uint (&data)) > 0) { + self->ifaces = g_new (GType, self->n_ifaces); + for (i = 0; i < self->n_ifaces; ++i) { + if (G_UNLIKELY ((self->ifaces[i] = clapper_cache_read_iface (&data)) == 0)) + goto abort_reading; + } + } + + /* Restore ParamSpecs */ + if ((self->n_pspecs = clapper_cache_read_uint (&data)) > 0) { + self->pspecs = g_new (GParamSpec *, self->n_pspecs); + for (i = 0; i < self->n_pspecs; ++i) { + if (G_UNLIKELY ((self->pspecs[i] = clapper_cache_read_pspec (&data)) == NULL)) + goto abort_reading; + } + } + + g_mapped_file_unref (mapped_file); + + GST_DEBUG_OBJECT (self, "Filled proxy \"%s\" from cache, n_ifaces: %u, n_pspecs: %u", + self->friendly_name, self->n_ifaces, self->n_pspecs); + + return TRUE; + +abort_reading: + GST_ERROR_OBJECT (self, "Cache file is corrupted or invalid"); + + g_free (self->ifaces); + self->n_ifaces = 0; + + for (i = 0; i < self->n_pspecs; ++i) { + g_clear_pointer (&self->pspecs[i], g_param_spec_unref); + } + g_free (self->pspecs); + self->n_pspecs = 0; + + g_mapped_file_unref (mapped_file); + + return FALSE; +} + +void +clapper_enhancer_proxy_export_to_cache (ClapperEnhancerProxy *self) +{ + GByteArray *bytes; + GError *error = NULL; + gchar *filename; + gboolean data_ok = TRUE; + guint i; + + bytes = clapper_cache_create (); + + /* If cache disabled */ + if (G_UNLIKELY (bytes == NULL)) + return; + + filename = _build_cache_filename (self); + GST_TRACE_OBJECT (self, "Exporting data to cache file: \"%s\"", filename); + + /* Store version */ + clapper_cache_store_string (bytes, self->version); + + /* Store Interfaces */ + clapper_cache_store_uint (bytes, self->n_ifaces); + for (i = 0; i < self->n_ifaces; ++i) { + /* This should never happen, as we only store Clapper interfaces */ + if (G_UNLIKELY (!(data_ok = clapper_cache_store_iface (bytes, self->ifaces[i])))) { + g_warning ("Cannot cache enhancer \"%s\" (%s), as it contains" + " unsupported interface type \"%s\"", + self->friendly_name, self->module_name, g_type_name (self->ifaces[i])); + break; + } + } + + if (data_ok) { + /* Store ParamSpecs */ + clapper_cache_store_uint (bytes, self->n_pspecs); + for (i = 0; i < self->n_pspecs; ++i) { + /* Can happen if someone writes an enhancer with unsupported + * param spec type with ClapperEnhancerParamFlags set */ + if (G_UNLIKELY (!(data_ok = clapper_cache_store_pspec (bytes, self->pspecs[i])))) { + g_warning ("Cannot cache enhancer \"%s\" (%s), as it contains" + " property \"%s\" of unsupported type", + self->friendly_name, self->module_name, self->pspecs[i]->name); + break; + } + } + } + + if (data_ok && clapper_cache_write (filename, bytes, &error)) { + GST_TRACE_OBJECT (self, "Successfully exported data to cache file"); + } else if (error) { + GST_ERROR_OBJECT (self, "Could not cache data, reason: %s", error->message); + g_error_free (error); + } + + g_free (filename); + g_byte_array_free (bytes, TRUE); +} + +gboolean +clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *self, GObject *enhancer) +{ + GType enhancer_types[1] = { CLAPPER_TYPE_EXTRACTABLE }; + GType *ifaces; + GParamSpec **pspecs; + GParamFlags enhancer_flags; + guint i, j, n, write_index = 0; + + /* Filter to only Clapper interfaces */ + ifaces = g_type_interfaces (G_OBJECT_TYPE (enhancer), &n); + for (i = 0; i < n; ++i) { + for (j = 0; j < G_N_ELEMENTS (enhancer_types); ++j) { + if (ifaces[i] == enhancer_types[j]) { + ifaces[write_index++] = ifaces[i]; + break; // match found, do next iface + } + } + } + + /* Resize memory */ + self->n_ifaces = write_index; + self->ifaces = g_realloc (ifaces, self->n_ifaces * sizeof (GType)); + + /* Filter to only Clapper param specs */ + write_index = 0; + pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (enhancer), &n); + enhancer_flags = (CLAPPER_ENHANCER_PARAM_GLOBAL | CLAPPER_ENHANCER_PARAM_LOCAL); + for (i = 0; i < n; ++i) { + if (pspecs[i]->flags & enhancer_flags) + pspecs[write_index++] = g_param_spec_ref (pspecs[i]); + } + + /* Resize memory */ + self->n_pspecs = write_index; + self->pspecs = g_realloc (pspecs, self->n_pspecs * sizeof (GParamSpec *)); + + GST_DEBUG_OBJECT (self, "Filled proxy \"%s\" from instance, n_ifaces: %u, n_pspecs: %u", + self->friendly_name, self->n_ifaces, self->n_pspecs); + + return TRUE; +} + +GObject * +clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *self) +{ + return self->peas_info; +} + +static gboolean +_apply_config_cb (GQuark field_id, const GValue *value, GObject *enhancer) +{ + g_object_set_property (enhancer, g_quark_to_string (field_id), value); + return TRUE; +} + +void +clapper_enhancer_proxy_apply_current_config_to_enhancer (ClapperEnhancerProxy *self, GObject *enhancer) +{ + GSettings *settings = clapper_enhancer_proxy_get_settings (self); + GstStructure *merged_config = NULL; + guint i; + + GST_DEBUG_OBJECT (self, "Applying config to enhancer"); + + /* Lock here to ensure consistent local config */ + GST_OBJECT_LOCK (self); + + for (i = 0; i < self->n_pspecs; ++i) { + GParamSpec *pspec = self->pspecs[i]; + + /* Using "has_field", as set value might be %NULL */ + if ((pspec->flags & CLAPPER_ENHANCER_PARAM_LOCAL) + && gst_structure_has_field (self->local_config, pspec->name)) { + if (!merged_config) + merged_config = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + gst_structure_set_value (merged_config, pspec->name, + gst_structure_get_value (self->local_config, pspec->name)); + continue; // local config overshadows global one + } + if (settings && (pspec->flags & CLAPPER_ENHANCER_PARAM_GLOBAL)) { + GVariant *val = g_settings_get_value (settings, pspec->name); + GVariant *def = g_settings_get_default_value (settings, pspec->name); + + if (!g_variant_equal (val, def)) { + if (!merged_config) + merged_config = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_boolean (val), NULL); + break; + case G_TYPE_INT: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_int32 (val), NULL); + break; + case G_TYPE_UINT: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_uint32 (val), NULL); + break; + case G_TYPE_DOUBLE: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_double (val), NULL); + break; + case G_TYPE_STRING: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_string (val, NULL), NULL); + break; + default:{ + if (G_IS_PARAM_SPEC_ENUM (pspec)) { + gst_structure_set (merged_config, pspec->name, + G_TYPE_INT, g_variant_get_int32 (val), NULL); + break; + } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) { + gst_structure_set (merged_config, pspec->name, + G_TYPE_UINT, g_variant_get_uint32 (val), NULL); + break; + } + GST_ERROR_OBJECT (self, "Unsupported enhancer \"%s\" setting type: %s", + pspec->name, g_type_name (pspec->value_type)); + break; + } + } + } + + g_variant_unref (val); + g_variant_unref (def); + } + } + + GST_OBJECT_UNLOCK (self); + + g_clear_object (&settings); + + /* Nothing if no configurable properties + * or all have default values */ + if (merged_config) { + gst_structure_foreach (merged_config, (GstStructureForeachFunc) _apply_config_cb, enhancer); + gst_structure_free (merged_config); + } + + GST_DEBUG_OBJECT (self, "Enhancer config applied"); +} + +/** + * clapper_enhancer_proxy_get_friendly_name: + * @proxy: a #ClapperEnhancerProxy + * + * Get a name from enhancer plugin info file. + * Can be used for showing in UI and such. + * + * Name field in plugin info file is mandatory, + * so this function never returns %NULL. + * + * Returns: (not nullable): name of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_friendly_name (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->friendly_name; +} + +/** + * clapper_enhancer_proxy_get_module_name: + * @proxy: a #ClapperEnhancerProxy + * + * Get name of the module from enhancer plugin info file. + * This value is used to uniquely identify a particular plugin. + * + * Module name in plugin info file is mandatory, + * so this function never returns %NULL. + * + * Returns: (not nullable): name of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_module_name (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->module_name; +} + +/** + * clapper_enhancer_proxy_get_module_dir: + * @proxy: a #ClapperEnhancerProxy + * + * Get a path to the directory from which enhancer is loaded. + * + * Returns: (not nullable): installation directory of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_module_dir (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->module_dir; +} + +/** + * clapper_enhancer_proxy_get_description: + * @proxy: a #ClapperEnhancerProxy + * + * Get description from enhancer plugin info file. + * + * Returns: (nullable): description of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_description (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->description; +} + +/** + * clapper_enhancer_proxy_get_version: + * @proxy: a #ClapperEnhancerProxy + * + * Get version string from enhancer plugin info file. + * + * Returns: (nullable): version string of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->version; +} + +/** + * clapper_enhancer_proxy_get_extra_data: + * @proxy: a #ClapperEnhancerProxy + * @key: name of the data to lookup + * + * Get extra data from enhancer plugin info file specified by @key. + * + * External data in the plugin info file is prefixed with `X-`. + * For example `X-Schemes=https`. + * + * Returns: (nullable): extra data value of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *self, const gchar *key) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + g_return_val_if_fail (key != NULL, NULL); + +#if CLAPPER_WITH_ENHANCERS_LOADER + return peas_plugin_info_get_external_data ((const PeasPluginInfo *) self->peas_info, key); +#else + return NULL; +#endif +} + +/** + * clapper_enhancer_proxy_extra_data_lists_value: + * @proxy: a #ClapperEnhancerProxy + * @key: name of the data to lookup + * @value: string to check for + * + * A convenience function to check whether @proxy plugin file + * has an extra data field with @key that among separated list + * of values includes @value (works on single value lists too). + * + * For example, when extra data in the plugin is `X-Schemes=https;http`, + * calling this function with "X-Schemes" as key and "http" as value will + * return %TRUE. + * + * Returns: whether list named with @key existed and contained @value. + * + * Since: 0.10 + */ +gboolean +clapper_enhancer_proxy_extra_data_lists_value (ClapperEnhancerProxy *self, + const gchar *key, const gchar *value) +{ + const gchar *list_str; + + /* Remaining things are checked in "get_extra_data()" */ + g_return_val_if_fail (value != NULL, FALSE); + + if ((list_str = clapper_enhancer_proxy_get_extra_data (self, key))) { + gsize value_len = strlen (value); + guint i = 0; + + while (list_str[i] != '\0') { + guint end = i; + + while (list_str[end] != ';' && list_str[end] != '\0') + ++end; + + /* Compare letters count until separator and prefix of whole string */ + if (end - i == value_len && g_str_has_prefix (list_str + i, value)) + return TRUE; + + i = end; + + /* Move to the next letter after ';' */ + if (list_str[i] != '\0') + ++i; + } + } + + return FALSE; +} + +/** + * clapper_enhancer_proxy_get_target_interfaces: + * @proxy: a #ClapperEnhancerProxy + * @n_interfaces: (out): return location for the length of the returned array + * + * Get an array of interfaces that target enhancer implements. + * + * The returned array includes only Clapper specific interfaces + * for writing enhancers. Applications should not care about any + * other interface types that given enhancer is using internally. + * + * Returns: (transfer none) (nullable) (array length=n_interfaces): an array of #GType interfaces. + * + * Since: 0.10 + */ +GType * +clapper_enhancer_proxy_get_target_interfaces (ClapperEnhancerProxy *self, guint *n_interfaces) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + if (n_interfaces) + *n_interfaces = self->n_ifaces; + + return self->ifaces; +} + +/** + * clapper_enhancer_proxy_target_has_interface: + * @proxy: a #ClapperEnhancerProxy + * @iface_type: an interface #GType + * + * A convenience function to check if target enhancer implements given interface. + * + * This works only with Clapper specific interfaces as @iface_type + * for writing enhancers. Applications should not care about any + * other interface types that given enhancer is using internally. + * + * Returns: whether target implements given interface. + * + * Since: 0.10 + */ +gboolean +clapper_enhancer_proxy_target_has_interface (ClapperEnhancerProxy *self, GType iface_type) +{ + guint i; + + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), FALSE); + + for (i = 0; i < self->n_ifaces; ++i) { + if (self->ifaces[i] == iface_type) + return TRUE; + } + + return FALSE; +} + +/** + * clapper_enhancer_proxy_get_target_properties: + * @proxy: a #ClapperEnhancerProxy + * @n_properties: (out): return location for the length of the returned array + * + * Get an array of properties in target enhancer. + * + * Implementations can use this in order to find out what properties, type of + * their values (including valid ranges) are allowed to set for a given enhancer. + * + * Use [flags@Clapper.EnhancerParamFlags] against flags of given [class@GObject.ParamSpec] + * to find out whether they are local, global or neither of them (internal). + * + * The returned array includes only Clapper enhancer specific properties (global and local). + * Applications can not access any other properties that given enhancer is using internally. + * + * Returns: (transfer none) (nullable) (array length=n_properties): an array of #GParamSpec objects. + * + * Since: 0.10 + */ +GParamSpec ** +clapper_enhancer_proxy_get_target_properties (ClapperEnhancerProxy *self, guint *n_properties) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + if (n_properties) + *n_properties = self->n_pspecs; + + return self->pspecs; +} + +/** + * clapper_enhancer_proxy_get_settings: + * @proxy: a #ClapperEnhancerProxy + * + * Get #GSettings of an enhancer. + * + * Implementations can use this together with [method@Clapper.EnhancerProxy.get_target_properties] + * in order to allow user to configure global enhancer properties. + * + * Settings include only keys from properties with [flags@Clapper.EnhancerParamFlags.GLOBAL] + * flag and are meant ONLY for user to set. To configure application local enhancer properties, + * use [method@Clapper.EnhancerProxy.set_locally] instead. + * + * This function returns a new instance of #GSettings, so settings can be accessed + * from different threads if needed. + * + * Returns: (transfer full) (nullable): A new #GSettings instance for an enhancer. + * + * Since: 0.10 + */ +GSettings * +clapper_enhancer_proxy_get_settings (ClapperEnhancerProxy *self) +{ + GSettings *settings = NULL; + + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + /* Try to lazy load schemas */ + _init_schema (self); + + if (self->schema) + settings = g_settings_new_full (self->schema, NULL, NULL); + + return settings; +} + +static GParamSpec * +_find_target_pspec_by_name (ClapperEnhancerProxy *self, const gchar *name) +{ + guint i; + + name = g_intern_string (name); + for (i = 0; i < self->n_pspecs; ++i) { + GParamSpec *pspec = self->pspecs[i]; + + /* GParamSpec names are always interned */ + if (pspec->name == name) + return pspec; + } + + g_warning ("No property \"%s\" in target of \"%s\" (%s)", + name, self->friendly_name, self->module_name); + + return NULL; +} + +static gboolean +_structure_take_value_by_pspec (ClapperEnhancerProxy *self, + GstStructure *structure, GParamSpec *pspec, GValue *value) +{ + if (G_LIKELY (G_VALUE_TYPE (value) == pspec->value_type) + && !g_param_value_validate (pspec, value)) { + if (G_LIKELY (pspec->flags & self->scope)) { + gst_structure_take_value (structure, pspec->name, value); + return TRUE; + } else { + g_warning ("Trying to set \"%s\" (%s) target property \"%s\" that is outside of proxy %s scope", + self->friendly_name, self->module_name, pspec->name, + (self->scope == CLAPPER_ENHANCER_PARAM_GLOBAL) ? "GLOBAL" : "LOCAL"); + } + } else { + g_warning ("Wrong value type or out of range for \"%s\" (%s) target property \"%s\"", + self->friendly_name, self->module_name, pspec->name); + } + + return FALSE; +} + +/** + * clapper_enhancer_proxy_set_locally: + * @proxy: a #ClapperEnhancerProxy + * @first_property_name: name of the first property to configure + * @...: %NULL-terminated list of arguments + * + * Configure one or more properties which have [flags@Clapper.EnhancerParamFlags.LOCAL] + * flag set on the target enhancer instance. + * + * Implementations can use this together with [method@Clapper.EnhancerProxy.get_target_properties] + * in order to configure local enhancer properties. + * + * Arguments should be %NULL terminated list of property name and value to set + * (like [method@GObject.Object.set] arguments). + * + * Since: 0.10 + */ +void +clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *self, const gchar *first_property_name, ...) +{ + GstStructure *structure; + const gchar *name; + va_list args; + + g_return_if_fail (CLAPPER_IS_ENHANCER_PROXY (self)); + g_return_if_fail (first_property_name != NULL); + + if (G_UNLIKELY (self->scope != CLAPPER_ENHANCER_PARAM_LOCAL)) { + g_warning ("Trying to apply local config to a non-local enhancer proxy!"); + return; + } + + structure = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + va_start (args, first_property_name); + name = first_property_name; + + while (name) { + GParamSpec *pspec = _find_target_pspec_by_name (self, name); + + if (G_LIKELY (pspec != NULL)) { + GValue value = G_VALUE_INIT; + gchar *err = NULL; + + G_VALUE_COLLECT_INIT (&value, pspec->value_type, args, 0, &err); + if (G_UNLIKELY (err != NULL)) { + g_critical ("%s", err); + g_free (err); + g_value_unset (&value); + break; + } + + if (!_structure_take_value_by_pspec (self, structure, pspec, &value)) + g_value_unset (&value); + } else { + break; + } + + name = va_arg (args, const gchar *); + } + + va_end (args); + + if (G_UNLIKELY (gst_structure_n_fields (structure) == 0)) { + gst_structure_free (structure); + return; + } + + _update_local_config_from_structure (self, structure); + + /* TODO: _post_local_config instead of free if managed + * (for when managed interfaces are implemented) */ + + gst_structure_free (structure); +} + +/** + * clapper_enhancer_proxy_set_locally_with_table: (rename-to clapper_enhancer_proxy_set_locally) + * @proxy: a #ClapperEnhancerProxy + * @table: (transfer none) (element-type utf8 GObject.Value): a #GHashTable with property names and values + * + * Same as [method@Clapper.EnhancerProxy.set_locally], but to configure uses + * [struct@GLib.HashTable] with string keys and [struct@GObject.Value] as their values. + * + * Since: 0.10 + */ +void +clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *self, GHashTable *table) +{ + GstStructure *structure; + GHashTableIter iter; + gpointer key_ptr, val_ptr; + + g_return_if_fail (CLAPPER_IS_ENHANCER_PROXY (self)); + g_return_if_fail (table != NULL); + + if (G_UNLIKELY (self->scope != CLAPPER_ENHANCER_PARAM_LOCAL)) { + g_warning ("Trying to apply local config to a non-local enhancer proxy!"); + return; + } + + structure = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + g_hash_table_iter_init (&iter, table); + while (g_hash_table_iter_next (&iter, &key_ptr, &val_ptr)) { + const gchar *name = (const gchar *) key_ptr; + GParamSpec *pspec = _find_target_pspec_by_name (self, name); + + if (G_LIKELY (pspec != NULL)) { + GValue value_copy = G_VALUE_INIT; + + if (val_ptr) { + const GValue *value = (const GValue *) val_ptr; + g_value_init (&value_copy, G_VALUE_TYPE (value)); + g_value_copy (value, &value_copy); + } else { // when setting property to NULL + if (pspec->value_type == G_TYPE_STRING) { + g_value_init (&value_copy, G_TYPE_STRING); + g_value_set_string (&value_copy, NULL); + } else { + g_value_init (&value_copy, G_TYPE_POINTER); + g_value_set_pointer (&value_copy, NULL); + } + } + + if (!_structure_take_value_by_pspec (self, structure, pspec, &value_copy)) + g_value_unset (&value_copy); + } + } + + if (G_UNLIKELY (gst_structure_n_fields (structure) == 0)) { + gst_structure_free (structure); + return; + } + + _update_local_config_from_structure (self, structure); + + /* TODO: _post_local_config instead of free if managed + * (for when managed interfaces are implemented) */ + + gst_structure_free (structure); +} + +static void +clapper_enhancer_proxy_init (ClapperEnhancerProxy *self) +{ +} + +static void +clapper_enhancer_proxy_finalize (GObject *object) +{ + ClapperEnhancerProxy *self = CLAPPER_ENHANCER_PROXY_CAST (object); + guint i; + + GST_TRACE_OBJECT (self, "Finalize"); + + g_object_unref (self->peas_info); + g_free (self->ifaces); + + for (i = 0; i < self->n_pspecs; ++i) { + g_param_spec_unref (self->pspecs[i]); + } + g_free (self->pspecs); + + gst_clear_structure (&self->local_config); + g_clear_pointer (&self->schema, g_settings_schema_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_enhancer_proxy_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperEnhancerProxy *self = CLAPPER_ENHANCER_PROXY_CAST (object); + + switch (prop_id) { + case PROP_FRIENDLY_NAME: + g_value_set_string (value, clapper_enhancer_proxy_get_friendly_name (self)); + break; + case PROP_MODULE_NAME: + g_value_set_string (value, clapper_enhancer_proxy_get_module_name (self)); + break; + case PROP_MODULE_DIR: + g_value_set_string (value, clapper_enhancer_proxy_get_module_dir (self)); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, clapper_enhancer_proxy_get_description (self)); + break; + case PROP_VERSION: + g_value_set_string (value, clapper_enhancer_proxy_get_version (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_enhancer_proxy_class_init (ClapperEnhancerProxyClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerproxy", 0, + "Clapper Enhancer Proxy"); + + gobject_class->get_property = clapper_enhancer_proxy_get_property; + gobject_class->finalize = clapper_enhancer_proxy_finalize; + + /** + * ClapperEnhancerProxy:friendly-name: + * + * Name from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_FRIENDLY_NAME] = g_param_spec_string ("friendly-name", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:module-name: + * + * Module name from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_MODULE_NAME] = g_param_spec_string ("module-name", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:module-dir: + * + * Module directory. + * + * Since: 0.10 + */ + param_specs[PROP_MODULE_DIR] = g_param_spec_string ("module-dir", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:description: + * + * Description from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_DESCRIPTION] = g_param_spec_string ("description", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:version: + * + * Version from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_VERSION] = g_param_spec_string ("version", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} diff --git a/src/lib/clapper/clapper-enhancer-proxy.h b/src/lib/clapper/clapper-enhancer-proxy.h new file mode 100644 index 00000000..8ea32870 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy.h @@ -0,0 +1,80 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_ENHANCER_PROXY (clapper_enhancer_proxy_get_type()) +#define CLAPPER_ENHANCER_PROXY_CAST(obj) ((ClapperEnhancerProxy *)(obj)) + +CLAPPER_API +G_DECLARE_FINAL_TYPE (ClapperEnhancerProxy, clapper_enhancer_proxy, CLAPPER, ENHANCER_PROXY, GstObject) + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_friendly_name (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_module_name (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_module_dir (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_description (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *proxy, const gchar *key); + +CLAPPER_API +gboolean clapper_enhancer_proxy_extra_data_lists_value (ClapperEnhancerProxy *proxy, const gchar *key, const gchar *value); + +CLAPPER_API +GType * clapper_enhancer_proxy_get_target_interfaces (ClapperEnhancerProxy *proxy, guint *n_interfaces); + +CLAPPER_API +gboolean clapper_enhancer_proxy_target_has_interface (ClapperEnhancerProxy *proxy, GType iface_type); + +CLAPPER_API +GParamSpec ** clapper_enhancer_proxy_get_target_properties (ClapperEnhancerProxy *proxy, guint *n_properties); + +CLAPPER_API +GSettings * clapper_enhancer_proxy_get_settings (ClapperEnhancerProxy *proxy); + +CLAPPER_API +void clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *proxy, const gchar *first_property_name, ...) G_GNUC_NULL_TERMINATED; + +CLAPPER_API +void clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *proxy, GHashTable *table); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancers-loader-private.h b/src/lib/clapper/clapper-enhancers-loader-private.h index c2a50d9c..7d7070de 100644 --- a/src/lib/clapper/clapper-enhancers-loader-private.h +++ b/src/lib/clapper/clapper-enhancers-loader-private.h @@ -22,21 +22,15 @@ #include #include +#include "clapper-enhancer-proxy-list.h" +#include "clapper-enhancer-proxy.h" + G_BEGIN_DECLS G_GNUC_INTERNAL -void clapper_enhancers_loader_initialize (void); +void clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies); G_GNUC_INTERNAL -gboolean clapper_enhancers_loader_has_enhancers (GType iface_type); - -G_GNUC_INTERNAL -gchar ** clapper_enhancers_loader_get_schemes (GType iface_type); - -G_GNUC_INTERNAL -gboolean clapper_enhancers_loader_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); - -G_GNUC_INTERNAL -GObject * clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri); +GObject * clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type); G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c index 663cbb3b..1969376b 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -28,11 +28,11 @@ static HMODULE _enhancers_dll_handle = NULL; #endif #include "clapper-enhancers-loader-private.h" +#include "clapper-enhancer-proxy-list-private.h" +#include "clapper-enhancer-proxy-private.h" -#define ENHANCER_INTERFACES "X-Interfaces" -#define ENHANCER_SCHEMES "X-Schemes" -#define ENHANCER_HOSTS "X-Hosts" -#define ENHANCER_IFACE_NAME_FROM_TYPE(type) (g_type_name (type) + 7) // strip "Clapper" prefix +// Supported interfaces +#include "clapper-extractable.h" #define GST_CAT_DEFAULT clapper_enhancers_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -58,10 +58,11 @@ _import_enhancers (const gchar *enhancers_path) * Initializes #PeasEngine with directories that store enhancers. */ void -clapper_enhancers_loader_initialize (void) +clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) { const gchar *enhancers_path; gchar *custom_path = NULL; + guint i, n_items; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0, "Clapper Enhancer Loader"); @@ -74,9 +75,8 @@ clapper_enhancers_loader_initialize (void) win_base_dir = g_win32_get_package_installation_directory_of_module ( _enhancers_dll_handle); - /* FIXME: Avoid hardcoded major version */ custom_path = g_build_filename (win_base_dir, - "lib", "clapper-0.0", "enhancers", NULL); + "lib", CLAPPER_API_NAME, "enhancers", NULL); enhancers_path = custom_path; // assign temporarily g_free (win_base_dir); @@ -104,321 +104,85 @@ clapper_enhancers_loader_initialize (void) _import_enhancers (enhancers_path); } - if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_INFO) { - GListModel *list = (GListModel *) _engine; - guint i, n_items = g_list_model_get_n_items (list); + n_items = g_list_model_get_n_items ((GListModel *) _engine); + for (i = 0; i < n_items; ++i) { + PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item ((GListModel *) _engine, i); + ClapperEnhancerProxy *proxy; + gboolean filled; - for (i = 0; i < n_items; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - GST_INFO ("Found enhancer: %s (%s)", peas_plugin_info_get_name (info), - peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES)); - g_object_unref (info); + /* Clapper supports only 1 proxy per plugin. Each plugin can + * ship 1 class, but it can implement more than 1 interface. */ + proxy = clapper_enhancer_proxy_new_global_take ((GObject *) info); + + /* Try to fill missing data from cache (fast). + * Otherwise make an instance and fill missing data from it (slow). */ + if (!(filled = clapper_enhancer_proxy_fill_from_cache (proxy))) { + GObject *enhancer; + GType main_types[1] = { CLAPPER_TYPE_EXTRACTABLE }; + guint j; + + /* We cannot ask libpeas for "any" of our main interfaces, so try each one until found */ + for (j = 0; j < G_N_ELEMENTS (main_types); ++j) { + if ((enhancer = clapper_enhancers_loader_create_enhancer (proxy, main_types[j]))) { + filled = clapper_enhancer_proxy_fill_from_instance (proxy, enhancer); + g_object_unref (enhancer); + + clapper_enhancer_proxy_export_to_cache (proxy); + break; + } + } } - GST_INFO ("Clapper enhancers initialized, found: %u", n_items); + if (G_LIKELY (filled)) { + GST_INFO ("Found enhancer: \"%s\" (%s)", + clapper_enhancer_proxy_get_friendly_name (proxy), + clapper_enhancer_proxy_get_module_name (proxy)); + clapper_enhancer_proxy_list_take_proxy (proxies, proxy); + } else { + GST_WARNING ("Enhancer init failed: \"%s\" (%s)", + clapper_enhancer_proxy_get_friendly_name (proxy), + clapper_enhancer_proxy_get_module_name (proxy)); + gst_object_unref (proxy); + } } + clapper_enhancer_proxy_list_sort (proxies); + + GST_INFO ("Clapper enhancers initialized, found: %u", + clapper_enhancer_proxy_list_get_n_proxies (proxies)); + g_free (custom_path); } -static inline gboolean -_is_name_listed (const gchar *name, const gchar *list_str) -{ - gsize name_len = strlen (name); - guint i = 0; - - while (list_str[i] != '\0') { - guint end = i; - - while (list_str[end] != ';' && list_str[end] != '\0') - ++end; - - /* Compare letters count until separator and prefix of whole string */ - if (end - i == name_len && g_str_has_prefix (list_str + i, name)) - return TRUE; - - i = end; - - /* Move to the next letter after ';' */ - if (list_str[i] != '\0') - ++i; - } - - return FALSE; -} - /* - * clapper_enhancers_loader_get_info: - * @iface_type: an interface #GType - * @scheme: an URI scheme - * @host: (nullable): an URI host - * - * Returns: (transfer full) (nullable): available #PeasPluginInfo or %NULL. - */ -static PeasPluginInfo * -clapper_enhancers_loader_get_info (GType iface_type, const gchar *scheme, const gchar *host) -{ - GListModel *list = (GListModel *) _engine; - PeasPluginInfo *found_info = NULL; - guint i, n_plugins = g_list_model_get_n_items (list); - const gchar *iface_name; - gboolean is_https; - - if (n_plugins == 0) { - GST_INFO ("No Clapper enhancers found"); - return NULL; - } - - iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); - - /* Strip common subdomains, so plugins do not - * have to list all combinations */ - if (host) { - if (g_str_has_prefix (host, "www.")) - host += 4; - else if (g_str_has_prefix (host, "m.")) - host += 2; - } - - GST_INFO ("Enhancer check, iface: %s, scheme: %s, host: %s", - iface_name, scheme, GST_STR_NULL (host)); - - is_https = (g_str_has_prefix (scheme, "http") - && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))); - - if (!host && is_https) - return NULL; - - for (i = 0; i < n_plugins; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - const gchar *iface_names, *schemes, *hosts; - - if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { - GST_DEBUG ("Skipping enhancer without interfaces: %s", peas_plugin_info_get_name (info)); - g_object_unref (info); - continue; - } - if (!_is_name_listed (iface_name, iface_names)) { - g_object_unref (info); - continue; - } - if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) { - GST_DEBUG ("Skipping enhancer without schemes: %s", peas_plugin_info_get_name (info)); - g_object_unref (info); - continue; - } - if (!_is_name_listed (scheme, schemes)) { - g_object_unref (info); - continue; - } - if (is_https) { - if (!(hosts = peas_plugin_info_get_external_data (info, ENHANCER_HOSTS))) { - GST_DEBUG ("Skipping enhancer without hosts: %s", peas_plugin_info_get_name (info)); - g_object_unref (info); - continue; - } - if (!_is_name_listed (host, hosts)) { - g_object_unref (info); - continue; - } - } - - found_info = info; - break; - } - - return found_info; -} - -/* - * clapper_enhancers_loader_has_enhancers: - * @iface_type: an interface #GType - * - * Check if any enhancer implementing given interface type is available. - * - * Returns: whether any valid enhancer was found. - */ -gboolean -clapper_enhancers_loader_has_enhancers (GType iface_type) -{ - GListModel *list = (GListModel *) _engine; - const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); - guint i, n_plugins; - - GST_DEBUG ("Checking for any enhancers of type: \"%s\"", iface_name); - - n_plugins = g_list_model_get_n_items (list); - - for (i = 0; i < n_plugins; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - const gchar *iface_names; - - if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { - g_object_unref (info); - continue; - } - if (!_is_name_listed (iface_name, iface_names)) { - g_object_unref (info); - continue; - } - - /* Additional validation */ - if (!peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES) - || !peas_plugin_info_get_external_data (info, ENHANCER_HOSTS)) { - g_object_unref (info); - continue; - } - - GST_DEBUG ("Found valid enhancers of type: \"%s\"", iface_name); - g_object_unref (info); - - return TRUE; - } - - GST_DEBUG ("No available enhancers of type: \"%s\"", iface_name); - - return FALSE; -} - -/* - * clapper_enhancers_loader_get_schemes: - * @iface_type: an interface #GType - * - * Get all supported schemes for a given interface type. - * The returned array consists of unique strings (no duplicates). - * - * Returns: (transfer full): all supported schemes by enhancers of type. - */ -gchar ** -clapper_enhancers_loader_get_schemes (GType iface_type) -{ - GListModel *list = (GListModel *) _engine; - GSList *found_schemes = NULL, *fs; - const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); - gchar **schemes_strv; - guint i, n_plugins, n_schemes; - - GST_DEBUG ("Checking supported URI schemes for \"%s\"", iface_name); - - n_plugins = g_list_model_get_n_items (list); - - for (i = 0; i < n_plugins; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - const gchar *iface_names, *schemes; - gchar **tmp_strv; - gint j; - - if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { - g_object_unref (info); - continue; - } - if (!_is_name_listed (iface_name, iface_names)) { - g_object_unref (info); - continue; - } - if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) { - g_object_unref (info); - continue; - } - - tmp_strv = g_strsplit (schemes, ";", 0); - - for (j = 0; tmp_strv[j]; ++j) { - const gchar *scheme = tmp_strv[j]; - - if (!found_schemes || !g_slist_find_custom (found_schemes, - scheme, (GCompareFunc) strcmp)) { - found_schemes = g_slist_append (found_schemes, g_strdup (scheme)); - GST_INFO ("Found supported URI scheme: %s", scheme); - } - } - - g_strfreev (tmp_strv); - g_object_unref (info); - } - - n_schemes = g_slist_length (found_schemes); - schemes_strv = g_new0 (gchar *, n_schemes + 1); - - fs = found_schemes; - for (i = 0; i < n_schemes; ++i) { - schemes_strv[i] = fs->data; - fs = fs->next; - } - - GST_DEBUG ("Total found URI schemes: %u", n_schemes); - - /* Since string pointers were taken, - * free list without content */ - g_slist_free (found_schemes); - - return schemes_strv; -} - -/* - * clapper_enhancers_loader_check: + * clapper_enhancers_loader_create_enhancer: * @iface_type: a requested #GType - * @scheme: an URI scheme - * @host: (nullable): an URI host - * @name: (out) (optional) (transfer none): return location for found enhancer name + * @info: a #PeasPluginInfo * - * Checks if any enhancer can handle @uri without initializing loader - * or creating enhancer instance, thus this can be used freely from any thread. - * - * Returns: whether enhancer for given scheme and host is available. - */ -gboolean -clapper_enhancers_loader_check (GType iface_type, - const gchar *scheme, const gchar *host, const gchar **name) -{ - PeasPluginInfo *info; - - if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { - if (name) - *name = peas_plugin_info_get_name (info); - - g_object_unref (info); - - return TRUE; - } - - return FALSE; -} - -/* - * clapper_enhancers_loader_create_enhancer_for_uri: - * @iface_type: a requested #GType - * @uri: a #GUri - * - * Creates a new enhancer object for given URI. + * Creates a new enhancer object using @info. * * Enhancer should only be created and used within single thread. * * Returns: (transfer full) (nullable): a new enhancer instance. */ GObject * -clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri) +clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type) { GObject *enhancer = NULL; - PeasPluginInfo *info; - const gchar *scheme = g_uri_get_scheme (uri); - const gchar *host = g_uri_get_host (uri); + PeasPluginInfo *info = (PeasPluginInfo *) clapper_enhancer_proxy_get_peas_info (proxy); - if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { - g_mutex_lock (&load_lock); + g_mutex_lock (&load_lock); - if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) { - GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_name (info)); - } else if (!peas_engine_provides_extension (_engine, info, iface_type)) { - GST_ERROR ("No \"%s\" enhancer in plugin: %s", ENHANCER_IFACE_NAME_FROM_TYPE (iface_type), - peas_plugin_info_get_name (info)); - } else { - enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL); - } - - g_mutex_unlock (&load_lock); - g_object_unref (info); + if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) { + GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_module_name (info)); + } else if (!peas_engine_provides_extension (_engine, info, iface_type)) { + GST_LOG ("No \"%s\" enhancer in module: %s", g_type_name (iface_type), + peas_plugin_info_get_module_name (info)); + } else { + enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL); } + g_mutex_unlock (&load_lock); + return enhancer; } diff --git a/src/lib/clapper/clapper-enums.h b/src/lib/clapper/clapper-enums.h index a1576fcd..70dc5841 100644 --- a/src/lib/clapper/clapper-enums.h +++ b/src/lib/clapper/clapper-enums.h @@ -127,4 +127,28 @@ typedef enum CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT, } ClapperDiscovererDiscoveryMode; +/* NOTE: GStreamer uses param flags 8-16, so start with 17. */ +/** + * ClapperEnhancerParamFlags: + * @CLAPPER_ENHANCER_PARAM_GLOBAL: Use this flag for enhancer properties that should have global access scope. + * Such are meant for application `USER` to configure. + * @CLAPPER_ENHANCER_PARAM_LOCAL: Use this flag for enhancer properties that should have local access scope. + * Such are meant for `APPLICATION` to configure. + * @CLAPPER_ENHANCER_PARAM_FILEPATH: Use this flag for enhancer properties that store string with a file path. + * Applications can use this as a hint to show file selection instead of a text entry. + * @CLAPPER_ENHANCER_PARAM_DIRPATH: Use this flag for enhancer properties that store string with a directory path. + * Applications can use this as a hint to show directory selection instead of a text entry. + * + * Additional [flags@GObject.ParamFlags] to be set in enhancer plugins implementations. + * + * Since: 0.10 + */ +typedef enum +{ + CLAPPER_ENHANCER_PARAM_GLOBAL = 1 << 17, + CLAPPER_ENHANCER_PARAM_LOCAL = 1 << 18, + CLAPPER_ENHANCER_PARAM_FILEPATH = 1 << 19, + CLAPPER_ENHANCER_PARAM_DIRPATH = 1 << 20, +} ClapperEnhancerParamFlags; + G_END_DECLS diff --git a/src/lib/clapper/clapper-player-private.h b/src/lib/clapper/clapper-player-private.h index 21ef21a9..c042cbae 100644 --- a/src/lib/clapper/clapper-player-private.h +++ b/src/lib/clapper/clapper-player-private.h @@ -47,6 +47,8 @@ struct _ClapperPlayer ClapperFeaturesManager *features_manager; gint have_features; // atomic integer + ClapperEnhancerProxyList *enhancer_proxies; + /* This is different from queue current item as it is used/changed only * on player thread, so we can always update correct item without lock */ ClapperMediaItem *played_item; diff --git a/src/lib/clapper/clapper-player.c b/src/lib/clapper/clapper-player.c index e394b839..c84a10fd 100644 --- a/src/lib/clapper/clapper-player.c +++ b/src/lib/clapper/clapper-player.c @@ -49,6 +49,7 @@ #include "clapper-video-stream-private.h" #include "clapper-audio-stream-private.h" #include "clapper-subtitle-stream-private.h" +#include "clapper-enhancer-proxy-list-private.h" #include "clapper-enums-private.h" #include "clapper-utils-private.h" #include "../shared/clapper-shared-utils-private.h" @@ -77,6 +78,7 @@ enum PROP_VIDEO_STREAMS, PROP_AUDIO_STREAMS, PROP_SUBTITLE_STREAMS, + PROP_ENHANCER_PROXIES, PROP_AUTOPLAY, PROP_POSITION, PROP_SPEED, @@ -814,7 +816,11 @@ _element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self factory_name = g_intern_static_string (GST_OBJECT_NAME (factory)); GST_INFO_OBJECT (self, "Element setup: %s", factory_name); - if (factory_name == g_intern_static_string ("downloadbuffer")) { + if (factory_name == g_intern_static_string ("clapperenhancersrc")) { + g_object_set (element, + "enhancer-proxies", self->enhancer_proxies, + NULL); + } else if (factory_name == g_intern_static_string ("downloadbuffer")) { gchar *download_template; /* Only set props if we have download template */ @@ -1129,6 +1135,24 @@ clapper_player_get_subtitle_streams (ClapperPlayer *self) return self->subtitle_streams; } +/** + * clapper_player_get_enhancer_proxies: + * @player: a #ClapperPlayer + * + * Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects. + * + * Returns: (transfer none): a #ClapperEnhancerProxyList of enhancer proxies. + * + * Since: 0.10 + */ +ClapperEnhancerProxyList * +clapper_player_get_enhancer_proxies (ClapperPlayer *self) +{ + g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); + + return self->enhancer_proxies; +} + /** * clapper_player_set_autoplay: * @player: a #ClapperPlayer @@ -2297,6 +2321,11 @@ clapper_player_init (ClapperPlayer *self) self->subtitle_streams = clapper_stream_list_new (); gst_object_set_parent (GST_OBJECT_CAST (self->subtitle_streams), GST_OBJECT_CAST (self)); + self->enhancer_proxies = clapper_enhancer_proxy_list_new_named (NULL); + gst_object_set_parent (GST_OBJECT_CAST (self->enhancer_proxies), GST_OBJECT_CAST (self)); + + clapper_enhancer_proxy_list_fill_from_global_proxies (self->enhancer_proxies); + self->position_query = gst_query_new_position (GST_FORMAT_TIME); self->current_state = GST_STATE_NULL; @@ -2363,6 +2392,9 @@ clapper_player_finalize (GObject *object) gst_object_unparent (GST_OBJECT_CAST (self->subtitle_streams)); gst_object_unref (self->subtitle_streams); + gst_object_unparent (GST_OBJECT_CAST (self->enhancer_proxies)); + gst_object_unref (self->enhancer_proxies); + gst_query_unref (self->position_query); gst_clear_object (&self->collection); @@ -2394,6 +2426,9 @@ clapper_player_get_property (GObject *object, guint prop_id, case PROP_SUBTITLE_STREAMS: g_value_set_object (value, clapper_player_get_subtitle_streams (self)); break; + case PROP_ENHANCER_PROXIES: + g_value_set_object (value, clapper_player_get_enhancer_proxies (self)); + break; case PROP_AUTOPLAY: g_value_set_boolean (value, clapper_player_get_autoplay (self)); break; @@ -2593,6 +2628,20 @@ clapper_player_class_init (ClapperPlayerClass *klass) NULL, NULL, CLAPPER_TYPE_STREAM_LIST, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * ClapperPlayer:enhancer-proxies: + * + * List of available enhancers in the form of [class@Clapper.EnhancerProxy] objects. + * + * Use these to inspect available enhancers on the system and configure + * their properties on a per player instance basis. + * + * Since: 0.10 + */ + param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies", + NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * ClapperPlayer:autoplay: * diff --git a/src/lib/clapper/clapper-player.h b/src/lib/clapper/clapper-player.h index 48547168..c9c2f370 100644 --- a/src/lib/clapper/clapper-player.h +++ b/src/lib/clapper/clapper-player.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,9 @@ ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *player); CLAPPER_API ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *player); +CLAPPER_API +ClapperEnhancerProxyList * clapper_player_get_enhancer_proxies (ClapperPlayer *player); + CLAPPER_API void clapper_player_set_autoplay (ClapperPlayer *player, gboolean enabled); diff --git a/src/lib/clapper/clapper.h b/src/lib/clapper/clapper.h index dd5491e5..aacad5bf 100644 --- a/src/lib/clapper/clapper.h +++ b/src/lib/clapper/clapper.h @@ -19,9 +19,6 @@ #pragma once -#include -#include - #define __CLAPPER_INSIDE__ #include @@ -30,6 +27,9 @@ #include #include +#include +#include +#include #include #include #include @@ -59,17 +59,4 @@ #include #endif -G_BEGIN_DECLS - -CLAPPER_API -void clapper_init (int *argc, char **argv[]); - -CLAPPER_API -gboolean clapper_init_check (int *argc, char **argv[]); - -CLAPPER_API -gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); - -G_END_DECLS - #undef __CLAPPER_INSIDE__ diff --git a/src/lib/clapper/gst/clapper-enhancer-director-private.h b/src/lib/clapper/gst/clapper-enhancer-director-private.h index ebb025cf..39ae34b0 100644 --- a/src/lib/clapper/gst/clapper-enhancer-director-private.h +++ b/src/lib/clapper/gst/clapper-enhancer-director-private.h @@ -38,6 +38,6 @@ G_GNUC_INTERNAL ClapperEnhancerDirector * clapper_enhancer_director_new (void); G_GNUC_INTERNAL -ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GUri *uri, GCancellable *cancellable, GError **error); +ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error); G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-enhancer-director.c b/src/lib/clapper/gst/clapper-enhancer-director.c index cdb1a162..2664faf9 100644 --- a/src/lib/clapper/gst/clapper-enhancer-director.c +++ b/src/lib/clapper/gst/clapper-enhancer-director.c @@ -20,11 +20,17 @@ #include #include "clapper-enhancer-director-private.h" -#include "../clapper-enhancers-loader-private.h" +#include "../clapper-enhancer-proxy-private.h" #include "../clapper-extractable-private.h" #include "../clapper-harvest-private.h" #include "../../shared/clapper-shared-utils-private.h" +#include "../clapper-functionalities-availability.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include "../clapper-enhancers-loader-private.h" +#endif + #define GST_CAT_DEFAULT clapper_enhancer_director_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -38,6 +44,8 @@ G_DEFINE_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER_TYPE_ typedef struct { + ClapperEnhancerDirector *director; + GList *filtered_proxies; GUri *uri; GCancellable *cancellable; GError **error; @@ -46,37 +54,61 @@ typedef struct static gpointer clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data) { - ClapperExtractable *extractable = NULL; - ClapperHarvest *harvest = clapper_harvest_new (); + ClapperEnhancerDirector *self = data->director; + GList *el; + ClapperHarvest *harvest = NULL; gboolean success = FALSE, cached = FALSE; + GST_DEBUG_OBJECT (self, "Extraction start"); + /* Cancelled during thread switching */ if (g_cancellable_is_cancelled (data->cancellable)) - goto finish; + return NULL; /* TODO: Cache lookup */ if (cached) { - // success = fill harvest from cache - goto finish; + // if ((success = fill harvest from cache)) + // return harvest; } - extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri ( - CLAPPER_TYPE_EXTRACTABLE, data->uri)); + GST_DEBUG_OBJECT (self, "Enhancer proxies for URI: %u", + g_list_length (data->filtered_proxies)); - /* Check just before extract */ - if (g_cancellable_is_cancelled (data->cancellable)) - goto finish; + for (el = data->filtered_proxies; el; el = g_list_next (el)) { + ClapperEnhancerProxy *proxy = CLAPPER_ENHANCER_PROXY_CAST (el->data); + ClapperExtractable *extractable = NULL; - success = clapper_extractable_extract (extractable, data->uri, - harvest, data->cancellable, data->error); + /* Check just before extract */ + if (g_cancellable_is_cancelled (data->cancellable)) + break; + +#if CLAPPER_WITH_ENHANCERS_LOADER + extractable = CLAPPER_EXTRACTABLE_CAST ( + clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_EXTRACTABLE)); +#endif + + if (G_LIKELY (extractable != NULL)) { + clapper_enhancer_proxy_apply_current_config_to_enhancer (proxy, (GObject *) extractable); + + harvest = clapper_harvest_new (); // fresh harvest for each extractable + + success = clapper_extractable_extract (extractable, data->uri, + harvest, data->cancellable, data->error); + gst_object_unref (extractable); + + /* We are done with extractable, but keep its harvest */ + if (success) + break; + + /* Clear harvest and try again with next enhancer */ + g_clear_object (&harvest); + } + } /* Cancelled during extract */ - if (g_cancellable_is_cancelled (data->cancellable)) { + if (g_cancellable_is_cancelled (data->cancellable)) success = FALSE; - goto finish; - } -finish: if (success) { if (!cached) { /* TODO: Store in cache */ @@ -94,7 +126,7 @@ finish: } } - gst_clear_object (&extractable); + GST_DEBUG_OBJECT (self, "Extraction finish"); return harvest; } @@ -116,11 +148,14 @@ clapper_enhancer_director_new (void) } ClapperHarvest * -clapper_enhancer_director_extract (ClapperEnhancerDirector *self, GUri *uri, +clapper_enhancer_director_extract (ClapperEnhancerDirector *self, + GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error) { ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1); + data->director = self; + data->filtered_proxies = filtered_proxies; data->uri = uri; data->cancellable = cancellable; data->error = error; diff --git a/src/lib/clapper/gst/clapper-enhancer-src-private.h b/src/lib/clapper/gst/clapper-enhancer-src-private.h index 6aeca22e..4e313d74 100644 --- a/src/lib/clapper/gst/clapper-enhancer-src-private.h +++ b/src/lib/clapper/gst/clapper-enhancer-src-private.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include diff --git a/src/lib/clapper/gst/clapper-enhancer-src.c b/src/lib/clapper/gst/clapper-enhancer-src.c index 198c0157..9297e622 100644 --- a/src/lib/clapper/gst/clapper-enhancer-src.c +++ b/src/lib/clapper/gst/clapper-enhancer-src.c @@ -22,13 +22,18 @@ #include "clapper-enhancer-src-private.h" #include "clapper-enhancer-director-private.h" -#include "../clapper-extractable-private.h" +#include "../clapper-basic-functions.h" +#include "../clapper-enhancer-proxy.h" +#include "../clapper-enhancer-proxy-list.h" +#include "../clapper-extractable.h" #include "../clapper-harvest-private.h" -#include "../clapper-enhancers-loader-private.h" #define GST_CAT_DEFAULT clapper_enhancer_src_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); +#define CHECK_SCHEME_IS_HTTPS(scheme) (g_str_has_prefix (scheme, "http") \ + && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))) + struct _ClapperEnhancerSrc { GstPushSrc parent; @@ -40,12 +45,15 @@ struct _ClapperEnhancerSrc gchar *uri; GUri *guri; + + ClapperEnhancerProxyList *enhancer_proxies; }; enum { PROP_0, PROP_URI, + PROP_ENHANCER_PROXIES, PROP_LAST }; @@ -62,10 +70,172 @@ clapper_enhancer_src_uri_handler_get_type (GType type) return GST_URI_SRC; } -static gpointer -_get_schemes_once (gpointer user_data G_GNUC_UNUSED) +/* + * _make_schemes: + * + * Make supported schemes array for a given interface type. + * The returned array consists of unique strings (no duplicates). + * + * Returns: (transfer full): all supported schemes by enhancers of @iface_type. + */ +static gchar ** +_make_schemes (gpointer user_data G_GNUC_UNUSED) { - return clapper_enhancers_loader_get_schemes (CLAPPER_TYPE_EXTRACTABLE); + ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies (); + GSList *found_schemes = NULL, *fs; + gchar **schemes_strv; + guint i, n_schemes, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + + GST_DEBUG ("Checking for supported URI schemes"); + + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + const gchar *schemes; + + if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE) + && (schemes = clapper_enhancer_proxy_get_extra_data (proxy, "X-Schemes"))) { + gchar **tmp_strv; + gint j; + + tmp_strv = g_strsplit (schemes, ";", 0); + + for (j = 0; tmp_strv[j]; ++j) { + const gchar *scheme = tmp_strv[j]; + + if (!found_schemes || !g_slist_find_custom (found_schemes, + scheme, (GCompareFunc) strcmp)) { + found_schemes = g_slist_append (found_schemes, g_strdup (scheme)); + GST_INFO ("Found supported URI scheme: \"%s\"", scheme); + } + } + + g_strfreev (tmp_strv); + } + } + + n_schemes = g_slist_length (found_schemes); + schemes_strv = g_new0 (gchar *, n_schemes + 1); + + fs = found_schemes; + for (i = 0; i < n_schemes; ++i) { + schemes_strv[i] = fs->data; + fs = fs->next; + } + + GST_DEBUG ("Total found URI schemes: %u", n_schemes); + + /* Since string pointers were taken, + * free list without content */ + g_slist_free (found_schemes); + + return schemes_strv; +} + +static inline const gchar * +_host_fixup (const gchar *host) +{ + /* Strip common subdomains, so plugins do not + * have to list all combinations */ + if (g_str_has_prefix (host, "www.")) + host += 4; + else if (g_str_has_prefix (host, "m.")) + host += 2; + + return host; +} + +/* + * _enhancer_check_for_uri: + * @self: a #ClapperEnhancerSrc + * @uri: a #GUri + * + * Check whether there is at least one enhancer for @uri in global list. + * This is used to reject URI early, thus making playbin choose different + * source element. It uses global list, since at this stage element is not + * yet placed within pipeline, so it cannot get proxies from player. + * + * Returns: whether at least one enhancer advertises support for given URI. + */ +static gboolean +_enhancer_check_for_uri (ClapperEnhancerSrc *self, GUri *uri) +{ + ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies (); + gboolean is_https; + guint i, n_proxies; + const gchar *scheme = g_uri_get_scheme (uri); + const gchar *host = g_uri_get_host (uri); + + if (host) + host = _host_fixup (host); + + GST_INFO_OBJECT (self, "Enhancer check, scheme: \"%s\", host: \"%s\"", + scheme, GST_STR_NULL (host)); + + /* Whether "http(s)" scheme is used */ + is_https = CHECK_SCHEME_IS_HTTPS (scheme); + + if (!host && is_https) + return FALSE; + + n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE) + && clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme) + && (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) + return TRUE; + } + + return FALSE; +} + +/* + * _filter_enhancers_for_uri: + * @self: a #ClapperEnhancerSrc + * @proxies: a #ClapperEnhancerProxyList + * @uri: a #GUri + * + * Finds all enhancer proxies of target implementing "Extractable" + * interface, which advertise support for given @uri. + * + * Returns: (transfer full): A sublist in the form of #GList with proxies. + */ +static GList * +_filter_enhancers_for_uri (ClapperEnhancerSrc *self, + ClapperEnhancerProxyList *proxies, GUri *uri) +{ + GList *sublist = NULL; + guint i, n_proxies; + gboolean is_https; + const gchar *scheme = g_uri_get_scheme (uri); + const gchar *host = g_uri_get_host (uri); + + if (host) + host = _host_fixup (host); + + GST_INFO_OBJECT (self, "Enhancer filter, scheme: \"%s\", host: \"%s\"", + scheme, GST_STR_NULL (host)); + + /* Whether "http(s)" scheme is used */ + is_https = CHECK_SCHEME_IS_HTTPS (scheme); + + if (!host && is_https) + return NULL; + + n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE) + && clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme) + && (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) { + sublist = g_list_append (sublist, gst_object_ref (proxy)); + break; + } + } + + return sublist; } static const gchar *const * @@ -73,7 +243,7 @@ clapper_enhancer_src_uri_handler_get_protocols (GType type) { static GOnce schemes_once = G_ONCE_INIT; - g_once (&schemes_once, _get_schemes_once, NULL); + g_once (&schemes_once, (GThreadFunc) _make_schemes, NULL); return (const gchar *const *) schemes_once.retval; } @@ -130,8 +300,7 @@ clapper_enhancer_src_uri_handler_set_uri (GstURIHandler *handler, return FALSE; } - if (!clapper_enhancers_loader_check (CLAPPER_TYPE_EXTRACTABLE, - g_uri_get_scheme (guri), g_uri_get_host (guri), NULL)) { + if (!_enhancer_check_for_uri (self, guri)) { g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "None of the available enhancers can handle this URI"); g_uri_unref (guri); @@ -296,6 +465,8 @@ static GstFlowReturn clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf) { ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (push_src); + ClapperEnhancerProxyList *proxies; + GList *filtered_proxies; GUri *guri; GCancellable *cancellable; ClapperHarvest *harvest; @@ -316,12 +487,28 @@ clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf) self->director = clapper_enhancer_director_new (); GST_OBJECT_LOCK (self); + + if (G_LIKELY (self->enhancer_proxies != NULL)) { + GST_INFO_OBJECT (self, "Using enhancer proxies: %" GST_PTR_FORMAT, self->enhancer_proxies); + proxies = gst_object_ref (self->enhancer_proxies); + } else { + /* Compat for old ClapperDiscoverer feature that does not set this property */ + GST_WARNING_OBJECT (self, "Falling back to using global enhancer proxy list!"); + proxies = gst_object_ref (clapper_get_global_enhancer_proxies ()); + } + guri = g_uri_ref (self->guri); cancellable = g_object_ref (self->cancellable); + GST_OBJECT_UNLOCK (self); - harvest = clapper_enhancer_director_extract (self->director, guri, cancellable, &error); + filtered_proxies = _filter_enhancers_for_uri (self, proxies, guri); + gst_object_unref (proxies); + harvest = clapper_enhancer_director_extract (self->director, + filtered_proxies, guri, cancellable, &error); + + g_clear_list (&filtered_proxies, gst_object_unref); g_uri_unref (guri); g_object_unref (cancellable); @@ -385,6 +572,16 @@ clapper_enhancer_src_query (GstBaseSrc *base_src, GstQuery *query) return ret; } +static void +clapper_enhancer_src_set_enhancer_proxies (ClapperEnhancerSrc *self, + ClapperEnhancerProxyList *enhancer_proxies) +{ + GST_OBJECT_LOCK (self); + gst_object_replace ((GstObject **) &self->enhancer_proxies, + GST_OBJECT_CAST (enhancer_proxies)); + GST_OBJECT_UNLOCK (self); +} + static void clapper_enhancer_src_init (ClapperEnhancerSrc *self) { @@ -413,6 +610,7 @@ clapper_enhancer_src_finalize (GObject *object) g_clear_object (&self->cancellable); g_free (self->uri); g_clear_pointer (&self->guri, g_uri_unref); + gst_clear_object (&self->enhancer_proxies); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -433,6 +631,9 @@ clapper_enhancer_src_set_property (GObject *object, guint prop_id, } break; } + case PROP_ENHANCER_PROXIES: + clapper_enhancer_src_set_enhancer_proxies (self, g_value_get_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -485,6 +686,10 @@ clapper_enhancer_src_class_init (ClapperEnhancerSrcClass *klass) "URI", "URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies", + NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gst_element_class_add_static_pad_template (gstelement_class, &src_template); diff --git a/src/lib/clapper/gst/clapper-plugin.c b/src/lib/clapper/gst/clapper-plugin.c index 4c26efb2..7a758722 100644 --- a/src/lib/clapper/gst/clapper-plugin.c +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -21,31 +21,53 @@ #include +#include "../clapper-basic-functions.h" +#include "../clapper-enhancer-proxy.h" +#include "../clapper-enhancer-proxy-list.h" +#include "../clapper-extractable.h" + #include "clapper-plugin-private.h" -#include "../clapper-functionalities-availability.h" - -#if CLAPPER_WITH_ENHANCERS_LOADER #include "clapper-enhancer-src-private.h" -#include "../clapper-extractable-private.h" -#include "../clapper-enhancers-loader-private.h" -#endif - #include "clapper-uri-list-demux-private.h" +/* + * clapper_gst_plugin_has_enhancers: + * @iface_type: an interface #GType + * + * Check if any enhancer implementing given interface type is available. + * + * Returns: whether any enhancer was found. + */ +static gboolean +clapper_gst_plugin_has_enhancers (ClapperEnhancerProxyList *proxies, GType iface_type) +{ + guint i, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type)) + return TRUE; + } + + return FALSE; +} + gboolean clapper_gst_plugin_init (GstPlugin *plugin) { gboolean res = FALSE; + ClapperEnhancerProxyList *global_proxies; -#if CLAPPER_WITH_ENHANCERS_LOADER gst_plugin_add_dependency_simple (plugin, "CLAPPER_ENHANCERS_PATH", CLAPPER_ENHANCERS_PATH, NULL, GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY); + global_proxies = clapper_get_global_enhancer_proxies (); + /* Avoid registering an URI handler without schemes */ - if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE)) + if (clapper_gst_plugin_has_enhancers (global_proxies, CLAPPER_TYPE_EXTRACTABLE)) res |= GST_ELEMENT_REGISTER (clapperenhancersrc, plugin); -#endif res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin); diff --git a/src/lib/clapper/gst/clapper-uri-list-demux-private.h b/src/lib/clapper/gst/clapper-uri-list-demux-private.h index 2839673f..0ba953fd 100644 --- a/src/lib/clapper/gst/clapper-uri-list-demux-private.h +++ b/src/lib/clapper/gst/clapper-uri-list-demux-private.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index 4ca10ea5..d126de82 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -54,6 +54,8 @@ config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) config_h.set_quoted('PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') config_h.set_quoted('PLUGIN_DESC', 'Clapper elements') config_h.set_quoted('PLUGIN_LICENSE', 'LGPL') +config_h.set_quoted('CLAPPER_API_NAME', clapper_api_name) +config_h.set_quoted('CLAPPER_ENHANCERS_ID', 'com.github.rafostar.Clapper.Enhancers') config_h.set_quoted('CLAPPER_ENHANCERS_PATH', clapper_enhancers_dir) configure_file( @@ -109,6 +111,9 @@ clapper_headers = [ 'clapper.h', 'clapper-enums.h', 'clapper-audio-stream.h', + 'clapper-basic-functions.h', + 'clapper-enhancer-proxy.h', + 'clapper-enhancer-proxy-list.h', 'clapper-extractable.h', 'clapper-feature.h', 'clapper-harvest.h', @@ -127,9 +132,12 @@ clapper_headers = [ clapper_visibility_header, ] clapper_sources = [ - 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', + 'clapper-basic-functions.c', + 'clapper-cache.c', + 'clapper-enhancer-proxy.c', + 'clapper-enhancer-proxy-list.c', 'clapper-extractable.c', 'clapper-feature.c', 'clapper-features-bus.c', @@ -148,6 +156,8 @@ clapper_sources = [ 'clapper-utils.c', 'clapper-video-stream.c', 'gst/clapper-plugin.c', + 'gst/clapper-enhancer-src.c', + 'gst/clapper-enhancer-director.c', 'gst/clapper-uri-list-demux.c', '../shared/clapper-shared-utils.c', ] @@ -170,8 +180,6 @@ if clapper_with_enhancers_loader clapper_deps += peas_dep clapper_sources += [ 'clapper-enhancers-loader.c', - 'gst/clapper-enhancer-src.c', - 'gst/clapper-enhancer-director.c', ] clapper_available_functionalities += 'enhancers-loader' endif