From 3cb74e0611e4263724e308ff2eff544b85fc7fe5 Mon Sep 17 00:00:00 2001
From: Julian Bouzas <julian.bouzas@collabora.com>
Date: Tue, 19 Nov 2019 13:40:59 -0500
Subject: [PATCH] modules: remove simple policy

This will be replaced by a new policy based on configuration files
---
 modules/meson.build            |  11 -
 modules/module-simple-policy.c | 571 ---------------------------------
 src/wireplumber.conf           |  18 --
 3 files changed, 600 deletions(-)
 delete mode 100644 modules/module-simple-policy.c

diff --git a/modules/meson.build b/modules/meson.build
index 51d013e1..da51128c 100644
--- a/modules/meson.build
+++ b/modules/meson.build
@@ -64,14 +64,3 @@ shared_library(
   install_dir : wireplumber_module_dir,
   dependencies : [wp_dep, pipewire_dep],
 )
-
-shared_library(
-  'wireplumber-module-simple-policy',
-  [
-    'module-simple-policy.c'
-  ],
-  c_args : [common_c_args, '-DG_LOG_DOMAIN="m-simple-policy"'],
-  install : true,
-  install_dir : wireplumber_module_dir,
-  dependencies : [wp_dep, pipewire_dep],
-)
diff --git a/modules/module-simple-policy.c b/modules/module-simple-policy.c
deleted file mode 100644
index 15828d02..00000000
--- a/modules/module-simple-policy.c
+++ /dev/null
@@ -1,571 +0,0 @@
-/* WirePlumber
- *
- * Copyright © 2019 Collabora Ltd.
- *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <wp/wp.h>
-
-enum {
-  DIRECTION_SINK = 0,
-  DIRECTION_SOURCE
-};
-
-struct _WpSimplePolicy
-{
-  WpPolicy parent;
-  WpEndpoint *selected[2];
-  guint32 selected_ctl_id[2];
-  gchar *default_playback;
-  gchar *default_capture;
-  GVariant *role_priorities;
-  guint pending_rescan;
-};
-
-G_DECLARE_FINAL_TYPE (WpSimplePolicy, simple_policy, WP, SIMPLE_POLICY, WpPolicy)
-G_DEFINE_TYPE (WpSimplePolicy, simple_policy, WP_TYPE_POLICY)
-
-static void simple_policy_rescan (WpSimplePolicy *self);
-
-static void
-simple_policy_init (WpSimplePolicy *self)
-{
-}
-
-static void
-simple_policy_finalize (GObject *object)
-{
-  WpSimplePolicy *self = WP_SIMPLE_POLICY (object);
-
-  g_free (self->default_playback);
-  g_free (self->default_capture);
-  g_clear_pointer (&self->role_priorities, g_variant_unref);
-
-  if (self->pending_rescan)
-    g_source_remove (self->pending_rescan);
-
-  G_OBJECT_CLASS (simple_policy_parent_class)->finalize (object);
-}
-
-static void
-endpoint_notify_control_value (WpEndpoint * ep, guint control_id,
-    WpSimplePolicy *self)
-{
-  g_autoptr (GVariant) v = NULL;
-  const gchar *media_class;
-  guint32 tmp_id;
-  gint direction;
-  WpEndpoint *old_selected;
-  guint32 old_ctl_id;
-
-  /* when an endpoint becomes "selected", unselect
-   * all other endpoints of the same media class */
-
-  /* the already "selected" endpoint cannot become even more "selected",
-   * so we skip it */
-  if (ep == self->selected[DIRECTION_SINK] ||
-      ep == self->selected[DIRECTION_SOURCE])
-    return;
-
-  /* verify that the changed control is the "selected" */
-  tmp_id = wp_endpoint_find_control (ep, WP_STREAM_ID_NONE, "selected");
-  if (control_id != tmp_id)
-    return;
-
-  /* verify it changed to TRUE */
-  v = wp_endpoint_get_control_value (ep, control_id);
-  if (g_variant_get_boolean (v) == FALSE)
-    return;
-
-  media_class = wp_endpoint_get_media_class (ep);
-  direction = strstr(media_class, "Sink") ? DIRECTION_SINK : DIRECTION_SOURCE;
-
-  g_debug ("selected %s: %p, unselecting %p",
-      (direction == DIRECTION_SINK) ? "sink" : "source",
-      ep, self->selected);
-
-  old_selected = self->selected[direction];
-  old_ctl_id = self->selected_ctl_id[direction];
-  self->selected[direction] = ep;
-  self->selected_ctl_id[direction] = control_id;
-
-  /* update the control value */
-  wp_endpoint_set_control_value (old_selected, old_ctl_id,
-      g_variant_new_boolean (FALSE));
-
-  /* notify policy watchers that things have changed */
-  wp_policy_notify_changed (WP_POLICY (self));
-
-  /* rescan for clients that need to be linked */
-  simple_policy_rescan (self);
-}
-
-static void
-select_endpoint (WpSimplePolicy *self, gint direction, WpEndpoint *ep,
-    guint32 control_id)
-{
-  g_info ("selecting %s %p (%s)",
-      (direction == DIRECTION_SINK) ? "sink" : "source",
-      ep, wp_endpoint_get_name (ep));
-
-  self->selected[direction] = ep;
-  self->selected_ctl_id[direction] = control_id;
-
-  /* update the control value */
-  wp_endpoint_set_control_value (ep, control_id,
-      g_variant_new_boolean (TRUE));
-
-  /* notify policy watchers that things have changed */
-  wp_policy_notify_changed (WP_POLICY (self));
-
-  /* rescan for clients that need to be linked */
-  simple_policy_rescan (self);
-}
-
-static gboolean
-select_new_endpoint (WpSimplePolicy *self)
-{
-  g_autoptr (WpCore) core = NULL;
-  g_autoptr (WpPolicyManager) pmgr = NULL;
-  g_autoptr (GPtrArray) ptr_array = NULL;
-  const gchar *media_class = NULL;
-  WpEndpoint *other;
-  guint32 control_id;
-  gint direction, i;
-
-  if (!self->selected[DIRECTION_SINK]) {
-    direction = DIRECTION_SINK;
-    media_class = "Audio/Sink";
-  } else if (!self->selected[DIRECTION_SOURCE]) {
-    direction = DIRECTION_SOURCE;
-    media_class = "Audio/Source";
-  } else
-    return G_SOURCE_REMOVE;
-
-  core = wp_policy_get_core (WP_POLICY (self));
-  pmgr = wp_policy_manager_get_instance (core);
-
-  /* Get all the endpoints with the same media class */
-  ptr_array = wp_policy_manager_list_endpoints (pmgr, media_class);
-
-  /* select the first available that has the "selected" control */
-  for (i = 0; i < (ptr_array ? ptr_array->len : 0); i++) {
-    other = g_ptr_array_index (ptr_array, i);
-
-    control_id =
-        wp_endpoint_find_control (other, WP_STREAM_ID_NONE, "selected");
-    if (control_id == WP_CONTROL_ID_NONE)
-      continue;
-
-    select_endpoint (self, direction, other, control_id);
-    break;
-  }
-
-  return G_SOURCE_REMOVE;
-}
-
-static void
-simple_policy_endpoint_added (WpPolicy *policy, WpEndpoint *ep)
-{
-  WpSimplePolicy *self = WP_SIMPLE_POLICY (policy);
-  const gchar *media_class = wp_endpoint_get_media_class (ep);
-  guint32 control_id;
-  gint direction;
-
-  /* we only care about alsa device endpoints here */
-  if (!g_str_has_prefix (media_class, "Audio/"))
-    return;
-
-  /* verify it has the "selected" control available */
-  control_id = wp_endpoint_find_control (ep, WP_STREAM_ID_NONE, "selected");
-  if (control_id == WP_CONTROL_ID_NONE)
-    return;
-
-  /* connect to control value changes */
-  g_debug ("connecting to notify-control-value for %p", ep);
-  g_signal_connect_object (ep, "notify-control-value",
-      (GCallback) endpoint_notify_control_value, self, 0);
-
-  /* select this endpoint if no other is already selected */
-  direction = strstr (media_class, "Sink") ? DIRECTION_SINK : DIRECTION_SOURCE;
-
-  if (!self->selected[direction]) {
-    select_endpoint (self, direction, ep, control_id);
-  } else {
-    /* we already have a selected endpoint, but maybe this one is better... */
-    const gchar *new_name = wp_endpoint_get_name (ep);
-    const gchar *default_dev = (direction == DIRECTION_SINK) ?
-        self->default_playback : self->default_capture;
-
-    /* FIXME: this is a crude way of searching for properties;
-     * we should have an API here */
-    if ((default_dev && strstr (new_name, default_dev)) ||
-        (!default_dev && strstr (new_name, "hw:0,0")))
-    {
-      wp_endpoint_set_control_value (self->selected[direction],
-          self->selected_ctl_id[direction],
-          g_variant_new_boolean (FALSE));
-      select_endpoint (self, direction, ep, control_id);
-    }
-  }
-}
-
-static void
-simple_policy_endpoint_removed (WpPolicy *policy, WpEndpoint *ep)
-{
-  WpSimplePolicy *self = WP_SIMPLE_POLICY (policy);
-  gint direction;
-
-  simple_policy_rescan (self);
-
-  /* if the "selected" endpoint was removed, select another one */
-
-  if (ep == self->selected[DIRECTION_SINK])
-    direction = DIRECTION_SINK;
-  else if (ep == self->selected[DIRECTION_SOURCE])
-    direction = DIRECTION_SOURCE;
-  else
-    return;
-
-  self->selected[direction] = NULL;
-  self->selected_ctl_id[direction] = WP_CONTROL_ID_NONE;
-
-  /* do the rest later, to possibly let other endpoints be removed as well
-   * before we try to pick a new one, to avoid multiple notifications
-   * and to also avoid crashing when the pipewire remote disconnects
-   * (in which case all endpoints are getting removed and changing controls
-   * triggers a crash in remote-endpoint, trying to export a value change
-   * without a valid connection)
-   */
-  g_idle_add_full (G_PRIORITY_HIGH, (GSourceFunc) select_new_endpoint,
-      g_object_ref (self), g_object_unref);
-}
-
-static void
-on_endpoint_link_created(GObject *initable, GAsyncResult *res, gpointer d)
-{
-  g_autoptr (WpEndpointLink) link = NULL;
-  g_autoptr (GError) error = NULL;
-  g_autoptr (WpEndpoint) src_ep = NULL;
-  g_autoptr (WpEndpoint) sink_ep = NULL;
-
-  /* Get the link */
-  link = wp_endpoint_link_new_finish(initable, res, &error);
-
-  /* Log linking info */
-  if (error) {
-    g_warning ("Could not link endpoints: %s\n", error->message);
-  } else {
-    g_return_if_fail (link);
-    src_ep = wp_endpoint_link_get_source_endpoint (link);
-    sink_ep = wp_endpoint_link_get_sink_endpoint (link);
-    g_info ("Sucessfully linked '%s' to '%s'\n", wp_endpoint_get_name (src_ep),
-        wp_endpoint_get_name (sink_ep));
-  }
-}
-
-static void
-handle_client (WpPolicy *policy, WpEndpoint *ep)
-{
-  const char *media_class = wp_endpoint_get_media_class(ep);
-  GVariantDict d;
-  g_autoptr (WpCore) core = NULL;
-  g_autoptr (WpEndpoint) target = NULL;
-  guint32 stream_id;
-  gboolean is_capture = FALSE;
-  gboolean is_persistent = FALSE;
-  g_autofree gchar *role = NULL;
-
-  /* Detect if the client is doing capture or playback */
-  is_capture = (strstr (media_class, "Stream/Input") != NULL);
-  is_persistent = g_str_has_prefix (media_class, "Persistent/");
-
-  /* Locate the target endpoint */
-  g_variant_dict_init (&d, NULL);
-  g_variant_dict_insert (&d, "action", "s", "link");
-  g_variant_dict_insert (&d, "media.class", "s",
-      is_capture ? "Audio/Source" : "Audio/Sink");
-
-  g_object_get (ep, "role", &role, NULL);
-  if (role)
-    g_variant_dict_insert (&d, "media.role", "s", role);
-
-  /* TODO: more properties are needed here */
-
-  core = wp_policy_get_core (policy);
-  target = wp_policy_find_endpoint (core, g_variant_dict_end (&d), &stream_id);
-  if (!target) {
-    g_warning ("Could not find target endpoint");
-    return;
-  }
-
-  /* if the client is already linked... */
-  if (wp_endpoint_is_linked (ep)) {
-    g_autoptr (WpEndpoint) existing_target = NULL;
-    GPtrArray *links = wp_endpoint_get_links (ep);
-    WpEndpointLink *l = g_ptr_array_index (links, 0);
-
-    existing_target = is_capture ?
-        wp_endpoint_link_get_source_endpoint (l) :
-        wp_endpoint_link_get_sink_endpoint (l);
-
-    if (existing_target == target) {
-      /* ... do nothing if it's already linked to the correct target */
-      g_debug ("Client '%s' already linked correctly",
-          wp_endpoint_get_name (ep));
-      return;
-    } else {
-      /* ... or else unlink it and continue */
-      g_debug ("Unlink client '%s' from its previous target",
-          wp_endpoint_get_name (ep));
-      wp_endpoint_link_destroy (l);
-    }
-  }
-
-  /* Unlink the target if it is already linked */
-  /* At this point we are certain that if the target is linked, it is linked
-   * with another client. If it was linked with @ep, we would have catched it
-   * above, where we check if the client is linked.
-   * In the capture case, we don't care, we just allow all clients to capture
-   * from the same device.
-   * In the playback case, we are certain that @ep has higher priority because
-   * this function is being called after sorting all the client endpoints
-   * and therefore we can safely unlink the previous client
-   */
-  if (!is_capture && !is_persistent && wp_endpoint_is_linked (target)) {
-    g_debug ("Unlink target '%s' from other clients",
-        wp_endpoint_get_name (target));
-    wp_endpoint_unlink (target);
-  }
-
-  /* Link the client with the target */
-  if (is_capture) {
-    wp_endpoint_link_new (core, target, stream_id, ep, WP_STREAM_ID_NONE,
-        on_endpoint_link_created, NULL);
-  } else {
-    wp_endpoint_link_new (core, ep, WP_STREAM_ID_NONE, target, stream_id,
-        on_endpoint_link_created, NULL);
-  }
-}
-
-static gint
-compare_client_priority (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-  GVariant *v = user_data;
-  WpEndpoint *ae = *(const gpointer *) a;
-  WpEndpoint *be = *(const gpointer *) b;
-  gint ret = 0;
-
-  /* if no role priorities are specified, we treat all roles as equal */
-  if (v) {
-    g_autofree gchar *a_role = NULL;
-    g_autofree gchar *b_role = NULL;
-    gint a_priority = 0, b_priority = 0;
-
-    g_object_get (ae, "role", &a_role, NULL);
-    g_object_get (be, "role", &b_role, NULL);
-
-    if (a_role)
-      g_variant_lookup (v, a_role, "i", &a_priority);
-    if (b_role)
-      g_variant_lookup (v, b_role, "i", &b_priority);
-
-    /* return b - a in order to sort descending */
-    ret = b_priority - a_priority;
-  }
-
-  /* when role priority is equal, the newest client wins */
-  if (ret == 0) {
-    guint64 a_time = wp_endpoint_get_creation_time (ae);
-    guint64 b_time = wp_endpoint_get_creation_time (be);
-
-    /* since a_time and b_time are expressed in system monotonic time,
-     * there is absolutely no chance that they will be equal */
-    ret = (b_time > a_time) ? 1 : -1;
-  }
-
-
-  return ret;
-}
-
-static gboolean
-simple_policy_rescan_in_idle (WpSimplePolicy *self)
-{
-  g_autoptr (WpCore) core = wp_policy_get_core (WP_POLICY (self));
-  g_autoptr (WpPolicyManager) pmgr = wp_policy_manager_get_instance (core);
-  g_autoptr (GPtrArray) endpoints = NULL;
-  WpEndpoint *ep;
-  gint i;
-
-  g_debug ("rescanning for clients that need linking");
-
-  endpoints = wp_policy_manager_list_endpoints (pmgr,
-      "Stream/Input/Audio");
-  if (endpoints) {
-    /* link all capture clients */
-    for (i = 0; i < endpoints->len; i++) {
-      ep = g_ptr_array_index (endpoints, i);
-      handle_client (WP_POLICY (self), ep);
-    }
-  }
-  g_clear_pointer (&endpoints, g_ptr_array_unref);
-
-  endpoints = wp_policy_manager_list_endpoints (pmgr,
-      "Persistent/Stream/Input/Audio");
-  if (endpoints) {
-    /* link all persistent capture clients */
-    for (i = 0; i < endpoints->len; i++) {
-      ep = g_ptr_array_index (endpoints, i);
-      handle_client (WP_POLICY (self), ep);
-    }
-  }
-  g_clear_pointer (&endpoints, g_ptr_array_unref);
-
-  endpoints = wp_policy_manager_list_endpoints (pmgr,
-      "Stream/Output/Audio");
-  if (endpoints && endpoints->len > 0) {
-    /* sort based on role priorities */
-    g_ptr_array_sort_with_data (endpoints, compare_client_priority,
-        self->role_priorities);
-
-    /* link the highest priority client */
-    ep = g_ptr_array_index (endpoints, 0);
-    handle_client (WP_POLICY (self), ep);
-  }
-  g_clear_pointer (&endpoints, g_ptr_array_unref);
-
-  endpoints = wp_policy_manager_list_endpoints (pmgr,
-      "Persistent/Stream/Output/Audio");
-  if (endpoints) {
-    /* link all persistent output clients */
-    for (i = 0; i < endpoints->len; i++) {
-      ep = g_ptr_array_index (endpoints, i);
-      handle_client (WP_POLICY (self), ep);
-    }
-  }
-  g_clear_pointer (&endpoints, g_ptr_array_unref);
-
-  self->pending_rescan = 0;
-  return G_SOURCE_REMOVE;
-}
-
-static void
-simple_policy_rescan (WpSimplePolicy *self)
-{
-  if (!self->pending_rescan)
-    self->pending_rescan = g_idle_add (
-        (GSourceFunc) simple_policy_rescan_in_idle, self);
-}
-
-static gboolean
-simple_policy_handle_endpoint (WpPolicy *policy, WpEndpoint *ep)
-{
-  WpSimplePolicy *self = WP_SIMPLE_POLICY (policy);
-  const char *media_class = NULL;
-
-  /* TODO: For now we only accept audio stream clients */
-  media_class = wp_endpoint_get_media_class(ep);
-  if (!g_str_has_suffix (media_class, "Audio")) {
-    return FALSE;
-  }
-
-  /* Schedule a rescan that will handle the endpoint */
-  simple_policy_rescan (self);
-  return TRUE;
-}
-
-static WpEndpoint *
-simple_policy_find_endpoint (WpPolicy *policy, GVariant *props,
-    guint32 *stream_id)
-{
-  g_autoptr (WpCore) core = NULL;
-  g_autoptr (WpPolicyManager) pmgr = NULL;
-  g_autoptr (GPtrArray) ptr_array = NULL;
-  const char *action = NULL;
-  const char *media_class = NULL;
-  const char *role = NULL;
-  WpEndpoint *ep;
-  int i;
-
-  core = wp_policy_get_core (policy);
-  pmgr = wp_policy_manager_get_instance (core);
-
-  g_variant_lookup (props, "action", "&s", &action);
-
-  /* Get all the endpoints with the specific media class*/
-  g_variant_lookup (props, "media.class", "&s", &media_class);
-  ptr_array = wp_policy_manager_list_endpoints (pmgr, media_class);
-  if (!ptr_array)
-    return NULL;
-
-  /* Get the endpoint with the "selected" flag (if it is an alsa endpoint) */
-  for (i = 0; i < ptr_array->len; i++) {
-    ep = g_ptr_array_index (ptr_array, i);
-    if (g_str_has_prefix (media_class, "Audio/")) {
-      g_autoptr (GVariant) value = NULL;
-      guint id;
-
-      id = wp_endpoint_find_control (ep, WP_STREAM_ID_NONE, "selected");
-      if (id == WP_CONTROL_ID_NONE)
-        continue;
-
-      value = wp_endpoint_get_control_value (ep, id);
-      if (value && g_variant_get_boolean (value)) {
-        g_object_ref (ep);
-        goto select_stream;
-      }
-    }
-  }
-
-  /* If not found, return the first endpoint */
-  ep = (ptr_array->len > 0) ?
-    g_object_ref (g_ptr_array_index (ptr_array, 0)) : NULL;
-
-select_stream:
-  g_variant_lookup (props, "media.role", "&s", &role);
-  if (!g_strcmp0 (action, "mixer") && !g_strcmp0 (role, "Master"))
-    *stream_id = WP_STREAM_ID_NONE;
-  else if (ep) {
-    /* the default role is "Multimedia" */
-    if (!role)
-      role = "Multimedia";
-    *stream_id = wp_endpoint_find_stream (ep, role);
-
-    /* role not found, try the first stream */
-    if (*stream_id == WP_STREAM_ID_NONE) {
-      g_warning ("role '%s' not found in endpoint", role);
-      *stream_id = 0;
-    }
-  }
-
-  return ep;
-}
-
-static void
-simple_policy_class_init (WpSimplePolicyClass *klass)
-{
-  GObjectClass *object_class = (GObjectClass *) klass;
-  WpPolicyClass *policy_class = (WpPolicyClass *) klass;
-
-  object_class->finalize = simple_policy_finalize;
-
-  policy_class->endpoint_added = simple_policy_endpoint_added;
-  policy_class->endpoint_removed = simple_policy_endpoint_removed;
-  policy_class->handle_endpoint = simple_policy_handle_endpoint;
-  policy_class->find_endpoint = simple_policy_find_endpoint;
-}
-
-void
-wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
-{
-  WpSimplePolicy *p = g_object_new (simple_policy_get_type (),
-      "rank", WP_POLICY_RANK_UPSTREAM,
-      NULL);
-  g_variant_lookup (args, "default-playback-device", "s", &p->default_playback);
-  g_variant_lookup (args, "default-capture-device", "s", &p->default_capture);
-  p->role_priorities = g_variant_lookup_value (args, "role-priorities",
-      G_VARIANT_TYPE ("a{si}"));
-  wp_policy_register (WP_POLICY (p), core);
-}
diff --git a/src/wireplumber.conf b/src/wireplumber.conf
index 96a5e090..58c12540 100644
--- a/src/wireplumber.conf
+++ b/src/wireplumber.conf
@@ -40,21 +40,3 @@ load-module C libwireplumber-module-pw-alsa-udev {
 # Monitors the Audio clients that are discovered via pipewire
 # and creates simple-endpoints for each one of them
 load-module C libwireplumber-module-pw-audio-client
-
-# Implements linking clients to devices and maintains
-# information about the devices to be used.
-# Notes:
-# - Devices must be specified in hw:X,Y format, where X and Y are integers.
-#   Things like hw:Intel,0 or paths are not understood.
-# - Roles and priorities can be arbitrary strings and arbitrary numbers
-# - Roles are matched against the stream names specified in the modules above.
-load-module C libwireplumber-module-simple-policy {
-  "default-playback-device": <"hw:0,0">,
-  "default-capture-device": <"hw:0,0">,
-  "role-priorities": <{
-    "Multimedia": 1,
-    "Communication": 5,
-    "Navigation": 8,
-    "Emergency": 10
-  }>
-}
-- 
GitLab