Skip to content
Snippets Groups Projects
remote-pipewire.c 6.37 KiB
/* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "remote-pipewire.h"
#include <pipewire/pipewire.h>

/**
 * Integration between the PipeWire main loop and GMainLoop
 */

#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)

typedef struct _WpLoopSource WpLoopSource;
struct _WpLoopSource
{
  GSource parent;
  struct pw_loop *loop;
};

static gboolean
wp_loop_source_dispatch (GSource * s, GSourceFunc callback, gpointer user_data)
{
  int result;

  pw_loop_enter (WP_LOOP_SOURCE(s)->loop);
  result = pw_loop_iterate (WP_LOOP_SOURCE(s)->loop, 0);
  pw_loop_leave (WP_LOOP_SOURCE(s)->loop);

  if (G_UNLIKELY (result < 0))
    g_warning ("pw_loop_iterate failed: %s", spa_strerror (result));

  return G_SOURCE_CONTINUE;
}

static void
wp_loop_source_finalize (GSource * s)
{
  pw_loop_destroy (WP_LOOP_SOURCE(s)->loop);
}

static GSourceFuncs source_funcs = {
  NULL,
  NULL,
  wp_loop_source_dispatch,
  wp_loop_source_finalize
};

static GSource *
wp_loop_source_new (void)
{
  GSource *s = g_source_new (&source_funcs, sizeof (WpLoopSource));
  WP_LOOP_SOURCE(s)->loop = pw_loop_new (NULL);

  g_source_add_unix_fd (s,
      pw_loop_get_fd (WP_LOOP_SOURCE(s)->loop),
      G_IO_IN | G_IO_ERR | G_IO_HUP);

  return (GSource *) s;
}

/**
 * WpRemotePipewire
 */

struct _WpRemotePipewire
{
  WpRemote parent;

  struct pw_core *core;
  struct pw_remote *remote;
  struct spa_hook remote_listener;

  GMainContext *context;
};

enum {
  PROP_0,
  PROP_STATE,
  PROP_ERROR_MESSAGE,
  PROP_PW_CORE,
  PROP_PW_REMOTE,
  PROP_CONTEXT,
};

G_DEFINE_TYPE (WpRemotePipewire, wp_remote_pipewire, WP_TYPE_REMOTE)

static void
on_remote_state_changed (void *d, enum pw_remote_state old_state,
    enum pw_remote_state new_state, const char *error)
{
  WpRemotePipewire *self = d;

  g_debug ("pipewire remote state changed, old:%s new:%s",
      pw_remote_state_as_string (old_state),
      pw_remote_state_as_string (new_state));

  g_object_notify (G_OBJECT (self), "state");
}

static const struct pw_remote_events remote_events = {
  PW_VERSION_REMOTE_EVENTS,
  .state_changed = on_remote_state_changed,
};

static void
wp_remote_pipewire_init (WpRemotePipewire *self)
{
}

static void
wp_remote_pipewire_constructed (GObject *object)
{
  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);
  GSource *source;

  source = wp_loop_source_new ();
  g_source_attach (source, self->context);

  self->core = pw_core_new (WP_LOOP_SOURCE (source)->loop, NULL, 0);

  self->remote = pw_remote_new (self->core, NULL, 0);
  pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events,
      self);

  G_OBJECT_CLASS (wp_remote_pipewire_parent_class)->constructed (object);
}

static void
wp_remote_pipewire_finalize (GObject *object)
{
  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);

  pw_remote_destroy (self->remote);
  pw_core_destroy (self->core);
  g_clear_pointer (&self->context, g_main_context_unref);

  G_OBJECT_CLASS (wp_remote_pipewire_parent_class)->finalize (object);
}

static void
wp_remote_pipewire_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);

  switch (property_id) {
  case PROP_STATE:
    /* enum pw_remote_state matches values with WpRemoteState */
    g_value_set_enum (value, pw_remote_get_state (self->remote, NULL));
    break;
  case PROP_ERROR_MESSAGE:
  {
    const gchar *msg;
    (void) pw_remote_get_state (self->remote, &msg);
    g_value_set_string (value, msg);
    break;
  }
  case PROP_PW_CORE:
    g_value_set_pointer (value, self->core);
    break;
  case PROP_PW_REMOTE:
    g_value_set_pointer (value, self->remote);
    break;
  case PROP_CONTEXT:
    g_value_set_boxed (value, self->context);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
wp_remote_pipewire_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);

  switch (property_id) {
  case PROP_CONTEXT:
    self->context = g_value_dup_boxed (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static gboolean
connect_in_idle (WpRemotePipewire *self)
{
  pw_remote_connect (self->remote);
  return G_SOURCE_REMOVE;
}

static gboolean
wp_remote_pipewire_connect (WpRemote *remote)
{
  WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (remote);
  g_autoptr (GSource) source;

  source = g_idle_source_new ();
  g_source_set_callback (source, (GSourceFunc) connect_in_idle, self, NULL);
  g_source_attach (source, self->context);
  return TRUE;
}

static void
wp_remote_pipewire_class_init (WpRemotePipewireClass *klass)
{
  GObjectClass *object_class = (GObjectClass *) klass;
  WpRemoteClass *remote_class = (WpRemoteClass *) klass;

  pw_init (NULL, NULL);

  object_class->constructed = wp_remote_pipewire_constructed;
  object_class->finalize = wp_remote_pipewire_finalize;
  object_class->get_property = wp_remote_pipewire_get_property;
  object_class->set_property = wp_remote_pipewire_set_property;

  remote_class->connect = wp_remote_pipewire_connect;

  g_object_class_override_property (object_class, PROP_STATE, "state");
  g_object_class_override_property (object_class, PROP_ERROR_MESSAGE,
      "error-message");

  g_object_class_install_property (object_class, PROP_CONTEXT,
      g_param_spec_boxed ("context", "context", "A GMainContext to attach to",
          G_TYPE_MAIN_CONTEXT,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PW_CORE,
      g_param_spec_pointer ("pw-core", "pw-core", "The pipewire core",
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (object_class, PROP_PW_REMOTE,
      g_param_spec_pointer ("pw-remote", "pw-remote", "The pipewire remote",
          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}

WpRemote *
wp_remote_pipewire_new (WpCore *core, GMainContext *context)
{
  WpRemote *remote;

  g_return_val_if_fail (WP_IS_CORE (core), NULL);

  remote = g_object_new (WP_TYPE_REMOTE_PIPEWIRE,
      "core", core,
      "context", context,
      NULL);
  wp_core_register_global (core, WP_GLOBAL_REMOTE_PIPEWIRE,
      g_object_ref (remote), g_object_unref);

  return remote;
}