From 98fdd7c58bcc829da6fe62b3e0910318aa397ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Sun, 27 Apr 2025 21:11:54 +0200 Subject: [PATCH 1/6] clapper: Introduce "ClapperEnhancerProxy" objects Add support for configuring Clapper Enhancers. In order to do that, introduce enhancer proxy object that act as intermediary between player and enhancer plugin. We cannot give direct access to enhancer instances from code, since they are managed (created and destroyed) by player as/when needed. Also due to some interpreted languages not working with multiple threads. Instead, give proxy objects that will store each enhancer configuration to be applied when an enhancer instance is created. With this, implementations also gain ability to browse available enhancers, see what they support and change their properties. Enhancers are now also assigned to player, instead of being only global. This allows to configure them separately on a per player instance basis. Writing configurable enhancers is super easy too, as all plugin has to do is install standard GParamSpec properties to its class with a corresponding gschema file (for global props only) and its done. --- .../clapper-enhancer-proxy-list-private.h | 41 + src/lib/clapper/clapper-enhancer-proxy-list.c | 329 ++++++ src/lib/clapper/clapper-enhancer-proxy-list.h | 53 + .../clapper/clapper-enhancer-proxy-private.h | 47 + src/lib/clapper/clapper-enhancer-proxy.c | 1035 +++++++++++++++++ src/lib/clapper/clapper-enhancer-proxy.h | 80 ++ .../clapper-enhancers-loader-private.h | 16 +- src/lib/clapper/clapper-enhancers-loader.c | 359 +----- src/lib/clapper/clapper-enums.h | 24 + src/lib/clapper/clapper-player-private.h | 2 + src/lib/clapper/clapper-player.c | 51 +- src/lib/clapper/clapper-player.h | 4 + src/lib/clapper/clapper.c | 73 +- src/lib/clapper/clapper.h | 7 +- .../gst/clapper-enhancer-director-private.h | 2 +- .../clapper/gst/clapper-enhancer-director.c | 73 +- src/lib/clapper/gst/clapper-enhancer-src.c | 220 +++- src/lib/clapper/gst/clapper-plugin.c | 39 +- src/lib/clapper/meson.build | 9 +- 19 files changed, 2108 insertions(+), 356 deletions(-) create mode 100644 src/lib/clapper/clapper-enhancer-proxy-list-private.h create mode 100644 src/lib/clapper/clapper-enhancer-proxy-list.c create mode 100644 src/lib/clapper/clapper-enhancer-proxy-list.h create mode 100644 src/lib/clapper/clapper-enhancer-proxy-private.h create mode 100644 src/lib/clapper/clapper-enhancer-proxy.c create mode 100644 src/lib/clapper/clapper-enhancer-proxy.h 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..e2756421 --- /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.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..570d93d1 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-private.h @@ -0,0 +1,47 @@ +/* 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 +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..a5f78d5a --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy.c @@ -0,0 +1,1035 @@ +/* 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.h" +#include "clapper-enhancer-proxy-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); +} + +gboolean +clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *self) +{ + GST_FIXME_OBJECT (self, "Implement enhancer proxy caching"); + + return FALSE; +} + +gboolean +clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *self, GObject *enhancer) +{ + self->ifaces = g_type_interfaces (G_OBJECT_TYPE (enhancer), &self->n_ifaces); + self->pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (enhancer), &self->n_pspecs); + + GST_DEBUG_OBJECT (self, "Filled proxy \"%s\", 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); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_object_unref (self->peas_info); + g_free (self->ifaces); + g_free (self->pspecs); + g_clear_pointer (&self->schema, g_settings_schema_unref); + gst_clear_structure (&self->local_config); + + 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..f5ed49c0 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -28,11 +28,13 @@ 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" + +// Supported interfaces +#include "clapper-extractable.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 #define GST_CAT_DEFAULT clapper_enhancers_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -58,10 +60,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"); @@ -104,321 +107,83 @@ 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); + + GST_FIXME_OBJECT (proxy, "Save enhancer proxy data to cache"); + 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_extra_data (proxy, ENHANCER_INTERFACES)); + clapper_enhancer_proxy_list_take_proxy (proxies, proxy); + } else { + GST_WARNING ("Enhancer \"%s\" init failed, skipping it", + clapper_enhancer_proxy_get_friendly_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.c b/src/lib/clapper/clapper.c index dfc50955..255d2a0f 100644 --- a/src/lib/clapper/clapper.c +++ b/src/lib/clapper/clapper.c @@ -27,12 +27,14 @@ #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" #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; @@ -51,8 +53,10 @@ clapper_init_check_internal (int *argc, char **argv[]) 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 +153,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.h b/src/lib/clapper/clapper.h index dd5491e5..8c94e56f 100644 --- a/src/lib/clapper/clapper.h +++ b/src/lib/clapper/clapper.h @@ -30,6 +30,8 @@ #include #include +#include +#include #include #include #include @@ -67,9 +69,12 @@ void clapper_init (int *argc, char **argv[]); CLAPPER_API gboolean clapper_init_check (int *argc, char **argv[]); -CLAPPER_API +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 #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.c b/src/lib/clapper/gst/clapper-enhancer-src.c index 198c0157..e0d8fa17 100644 --- a/src/lib/clapper/gst/clapper-enhancer-src.c +++ b/src/lib/clapper/gst/clapper-enhancer-src.c @@ -22,13 +22,17 @@ #include "clapper-enhancer-src-private.h" #include "clapper-enhancer-director-private.h" +#include "../clapper.h" +#include "../clapper-enhancer-proxy-list-private.h" #include "../clapper-extractable-private.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 +44,15 @@ struct _ClapperEnhancerSrc gchar *uri; GUri *guri; + + ClapperEnhancerProxyList *enhancer_proxies; }; enum { PROP_0, PROP_URI, + PROP_ENHANCER_PROXIES, PROP_LAST }; @@ -62,10 +69,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 +242,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 +299,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 +464,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 +486,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 +571,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 +609,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 +630,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 +685,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..f6d51165 100644 --- a/src/lib/clapper/gst/clapper-plugin.c +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -21,31 +21,50 @@ #include -#include "clapper-plugin-private.h" -#include "../clapper-functionalities-availability.h" - -#if CLAPPER_WITH_ENHANCERS_LOADER +#include "../clapper.h" #include "clapper-enhancer-src-private.h" -#include "../clapper-extractable-private.h" -#include "../clapper-enhancers-loader-private.h" -#endif +#include "clapper-plugin-private.h" #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/meson.build b/src/lib/clapper/meson.build index 4ca10ea5..f86d38ec 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -54,6 +54,7 @@ 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_ENHANCERS_ID', 'com.github.rafostar.Clapper.Enhancers') config_h.set_quoted('CLAPPER_ENHANCERS_PATH', clapper_enhancers_dir) configure_file( @@ -109,6 +110,8 @@ clapper_headers = [ 'clapper.h', 'clapper-enums.h', 'clapper-audio-stream.h', + 'clapper-enhancer-proxy.h', + 'clapper-enhancer-proxy-list.h', 'clapper-extractable.h', 'clapper-feature.h', 'clapper-harvest.h', @@ -130,6 +133,8 @@ clapper_sources = [ 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', + 'clapper-enhancer-proxy.c', + 'clapper-enhancer-proxy-list.c', 'clapper-extractable.c', 'clapper-feature.c', 'clapper-features-bus.c', @@ -148,6 +153,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 +177,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 From d951be7a56a7028482c2cbe15a256f4b5d4e9d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Mon, 5 May 2025 20:15:24 +0200 Subject: [PATCH 2/6] clapper: Remove usage of "X-Interfaces" in enhancer data At this point its sole remaining place where its used is one debug message. For this reason remove it. With this, enhancer plugin files no longer need to have this in them unless they want to keep Clapper 0.8 compat (otherwise unused). --- src/lib/clapper/clapper-enhancers-loader.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c index f5ed49c0..1e0d451c 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -34,8 +34,6 @@ static HMODULE _enhancers_dll_handle = NULL; // Supported interfaces #include "clapper-extractable.h" -#define ENHANCER_INTERFACES "X-Interfaces" - #define GST_CAT_DEFAULT clapper_enhancers_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -137,12 +135,14 @@ clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) } if (G_LIKELY (filled)) { - GST_INFO ("Found enhancer: %s (%s)", clapper_enhancer_proxy_get_friendly_name (proxy), - clapper_enhancer_proxy_get_extra_data (proxy, ENHANCER_INTERFACES)); + 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 \"%s\" init failed, skipping it", - clapper_enhancer_proxy_get_friendly_name (proxy)); + 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); } } From 3ef6e9694a7f70c95cc6dcff89ad9475718ed355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 8 May 2025 20:35:37 +0200 Subject: [PATCH 3/6] clapper-app: Add enhancers to preferences window Browse, read info and configure Clapper enhancer plugins from preferences window --- src/bin/clapper-app/clapper-app-file-dialog.c | 69 ++++ src/bin/clapper-app/clapper-app-file-dialog.h | 7 + .../clapper-app-preferences-window.c | 384 ++++++++++++++++++ src/bin/clapper-app/css/styles.css | 4 +- .../ui/clapper-app-preferences-window.ui | 138 ++++++- 5 files changed, 599 insertions(+), 3 deletions(-) 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 @@ From c6c4fe309b13177a4d805a5ad06d37bffd22fbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Mon, 12 May 2025 22:22:28 +0200 Subject: [PATCH 4/6] clapper: Implement data cache Add data cache functions and use them to store enhancer data into local cache file. This way we can restore all properties and interfaces used in enhancer without creating its instance. This avoids loading interpreters like Python at init time making startup a lot faster. --- src/lib/clapper/clapper-cache-private.h | 93 ++++ src/lib/clapper/clapper-cache.c | 491 ++++++++++++++++++ .../clapper/clapper-enhancer-proxy-private.h | 3 + src/lib/clapper/clapper-enhancer-proxy.c | 179 ++++++- src/lib/clapper/clapper-enhancers-loader.c | 2 +- src/lib/clapper/clapper.c | 2 + src/lib/clapper/meson.build | 2 + 7 files changed, 766 insertions(+), 6 deletions(-) create mode 100644 src/lib/clapper/clapper-cache-private.h create mode 100644 src/lib/clapper/clapper-cache.c 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-private.h b/src/lib/clapper/clapper-enhancer-proxy-private.h index 570d93d1..144a9c2d 100644 --- a/src/lib/clapper/clapper-enhancer-proxy-private.h +++ b/src/lib/clapper/clapper-enhancer-proxy-private.h @@ -38,6 +38,9 @@ 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); diff --git a/src/lib/clapper/clapper-enhancer-proxy.c b/src/lib/clapper/clapper-enhancer-proxy.c index a5f78d5a..703e053f 100644 --- a/src/lib/clapper/clapper-enhancer-proxy.c +++ b/src/lib/clapper/clapper-enhancer-proxy.c @@ -45,6 +45,7 @@ #include "clapper.h" #include "clapper-enhancer-proxy-private.h" +#include "clapper-cache-private.h" #include "clapper-extractable.h" #include "clapper-enums.h" @@ -308,21 +309,183 @@ _init_schema (ClapperEnhancerProxy *self) 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) { - GST_FIXME_OBJECT (self, "Implement enhancer proxy caching"); + 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) { - self->ifaces = g_type_interfaces (G_OBJECT_TYPE (enhancer), &self->n_ifaces); - self->pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (enhancer), &self->n_pspecs); + GType enhancer_types[1] = { CLAPPER_TYPE_EXTRACTABLE }; + GType *ifaces; + GParamSpec **pspecs; + GParamFlags enhancer_flags; + guint i, j, n, write_index = 0; - GST_DEBUG_OBJECT (self, "Filled proxy \"%s\", n_ifaces: %u, n_pspecs: %u", + /* 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; @@ -925,14 +1088,20 @@ 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); - g_clear_pointer (&self->schema, g_settings_schema_unref); + gst_clear_structure (&self->local_config); + g_clear_pointer (&self->schema, g_settings_schema_unref); G_OBJECT_CLASS (parent_class)->finalize (object); } diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c index 1e0d451c..63cf4c98 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -128,7 +128,7 @@ clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) filled = clapper_enhancer_proxy_fill_from_instance (proxy, enhancer); g_object_unref (enhancer); - GST_FIXME_OBJECT (proxy, "Save enhancer proxy data to cache"); + clapper_enhancer_proxy_export_to_cache (proxy); break; } } diff --git a/src/lib/clapper/clapper.c b/src/lib/clapper/clapper.c index 255d2a0f..8d181f59 100644 --- a/src/lib/clapper/clapper.c +++ b/src/lib/clapper/clapper.c @@ -23,6 +23,7 @@ #include #include "clapper.h" +#include "clapper-cache-private.h" #include "clapper-utils-private.h" #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" @@ -48,6 +49,7 @@ 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 (); diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index f86d38ec..7ed03631 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -54,6 +54,7 @@ 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) @@ -133,6 +134,7 @@ clapper_sources = [ 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', + 'clapper-cache.c', 'clapper-enhancer-proxy.c', 'clapper-enhancer-proxy-list.c', 'clapper-extractable.c', From a97e7d1a9697e909ee204fef186184361cf211a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Mon, 12 May 2025 22:57:51 +0200 Subject: [PATCH 5/6] clapper: Avoid using hardcoded API name in enhancers loader on win32 --- src/lib/clapper/clapper-enhancers-loader.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c index 63cf4c98..1969376b 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -75,9 +75,8 @@ clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) 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); From e34f729f625b7bd05675a3fd93cabc92509c3fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Fri, 16 May 2025 16:19:40 +0200 Subject: [PATCH 6/6] clapper: Move basic functions into separate files Avoid including whole "clapper.h" internally. Faster recompilation when changes are done. --- .../{clapper.c => clapper-basic-functions.c} | 6 ++- src/lib/clapper/clapper-basic-functions.h | 46 +++++++++++++++++++ src/lib/clapper/clapper-enhancer-proxy-list.c | 2 +- src/lib/clapper/clapper-enhancer-proxy.c | 3 +- src/lib/clapper/clapper.h | 20 +------- .../gst/clapper-enhancer-src-private.h | 1 + src/lib/clapper/gst/clapper-enhancer-src.c | 7 +-- src/lib/clapper/gst/clapper-plugin.c | 7 ++- .../gst/clapper-uri-list-demux-private.h | 1 + src/lib/clapper/meson.build | 3 +- 10 files changed, 67 insertions(+), 29 deletions(-) rename src/lib/clapper/{clapper.c => clapper-basic-functions.c} (98%) create mode 100644 src/lib/clapper/clapper-basic-functions.h diff --git a/src/lib/clapper/clapper.c b/src/lib/clapper/clapper-basic-functions.c similarity index 98% rename from src/lib/clapper/clapper.c rename to src/lib/clapper/clapper-basic-functions.c index 8d181f59..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,7 +22,7 @@ #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" @@ -31,6 +31,8 @@ #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 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-enhancer-proxy-list.c b/src/lib/clapper/clapper-enhancer-proxy-list.c index e2756421..619436bd 100644 --- a/src/lib/clapper/clapper-enhancer-proxy-list.c +++ b/src/lib/clapper/clapper-enhancer-proxy-list.c @@ -27,7 +27,7 @@ #include -#include "clapper.h" +#include "clapper-basic-functions.h" #include "clapper-enhancer-proxy-list-private.h" #include "clapper-enhancer-proxy-private.h" diff --git a/src/lib/clapper/clapper-enhancer-proxy.c b/src/lib/clapper/clapper-enhancer-proxy.c index 703e053f..cd70a9ce 100644 --- a/src/lib/clapper/clapper-enhancer-proxy.c +++ b/src/lib/clapper/clapper-enhancer-proxy.c @@ -43,8 +43,9 @@ #include -#include "clapper.h" #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" diff --git a/src/lib/clapper/clapper.h b/src/lib/clapper/clapper.h index 8c94e56f..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,7 @@ #include #include +#include #include #include #include @@ -61,20 +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_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 - #undef __CLAPPER_INSIDE__ 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 e0d8fa17..9297e622 100644 --- a/src/lib/clapper/gst/clapper-enhancer-src.c +++ b/src/lib/clapper/gst/clapper-enhancer-src.c @@ -22,9 +22,10 @@ #include "clapper-enhancer-src-private.h" #include "clapper-enhancer-director-private.h" -#include "../clapper.h" -#include "../clapper-enhancer-proxy-list-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" #define GST_CAT_DEFAULT clapper_enhancer_src_debug diff --git a/src/lib/clapper/gst/clapper-plugin.c b/src/lib/clapper/gst/clapper-plugin.c index f6d51165..7a758722 100644 --- a/src/lib/clapper/gst/clapper-plugin.c +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -21,10 +21,13 @@ #include -#include "../clapper.h" -#include "clapper-enhancer-src-private.h" +#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-enhancer-src-private.h" #include "clapper-uri-list-demux-private.h" /* 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 7ed03631..d126de82 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -111,6 +111,7 @@ 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', @@ -131,9 +132,9 @@ 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',